Erst sammeln, dann schnellen

Ein reiner Weg von A nach B sah aus, als würde ein Kasten verschoben.
Jetzt zwei Züge: die Leiste zieht sich erst in sich zusammen und zieht
dann erst um — wie ein Gummiband, das sich sammelt, bevor es schnellt.

Welche Richtung sich zuerst sammelt, hängt davon ab, wohin es geht. Wer
nach oben zieht, wird flach, also klappt zuerst die Höhe zusammen; wer
an die Seite zieht, wird schmal, also zuerst die Breite. Die
Zwischenstation steht deshalb noch am alten Ort, aber schon im Zielmaß
der einen Seite.

Nachgemessen: bei 220 ms ist die Breite von 2.27 auf 1.38 gefallen und
die Position unverändert — sie sammelt sich, ohne zu wandern. Bei 380 ms
ist sie unterwegs und zieht sich auf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-14 17:09:40 +02:00
co-authored by Claude Opus 5
parent 9320092ebf
commit 2e15cdfddb
+32 -9
View File
@@ -624,23 +624,46 @@ function moveNavigation(to) {
const after = island.getBoundingClientRect();
if (!before.width || !after.width) return;
const dx = before.left - after.left;
const dy = before.top - after.top;
const sx = before.width / after.width;
const sy = before.height / after.height;
// **Zwei Züge, nicht einer.**
//
// Ein reiner Weg von A nach B sieht aus, als würde ein Kasten verschoben.
// Schöner ist, was die Leiste erst *in sich* zusammenziehen lässt und dann
// erst umziehen — wie ein Gummiband, das sich sammelt, bevor es schnellt.
//
// Welche Richtung sich zuerst sammelt, hängt davon ab, wohin es geht:
// Wer nach **oben** zieht, wird flach — also klappt zuerst die Höhe
// zusammen, und die Breite kommt erst beim Hinüberschieben. Wer an die
// **Seite** zieht, wird schmal — also zuerst die Breite, die Höhe danach.
//
// Die Zwischenstation steht deshalb noch an der alten Stelle, aber schon
// mit dem Zielmaß in der einen Richtung.
const gathered = to === 'top'
? `translate(${dx}px, ${dy}px) scale(${sx}, 1)` // Höhe zuerst
: `translate(${dx}px, ${dy}px) scale(1, ${sy})`; // Breite zuerst
island.animate(
[
{
transformOrigin: 'top left',
transform: `translate(${before.left - after.left}px, ${before.top - after.top}px)`
+ ` scale(${before.width / after.width}, ${before.height / after.height})`,
},
{ transformOrigin: 'top left', transform: 'none' },
{ transformOrigin: 'top left', transform: `translate(${dx}px, ${dy}px) scale(${sx}, ${sy})`,
easing: 'cubic-bezier(.5,0,.75,0)', offset: 0 },
// Gesammelt — noch am alten Ort, aber schon im Zielmaß der einen Seite.
{ transformOrigin: 'top left', transform: gathered,
easing: 'cubic-bezier(.22,1,.36,1)', offset: 0.42 },
// Und hinüber. Das leichte Überschwingen am Ende ist das Gummiband.
{ transformOrigin: 'top left', transform: 'none', offset: 1 },
],
{ duration: 420, easing: 'cubic-bezier(.32,.72,0,1)' }
{ duration: 520 }
);
// Die Beschriftungen gegen die Stauchung aufblenden, damit aus dem
// Verziehen kein Lesen wird.
island.animate(
[{ opacity: 0.55 }, { opacity: 1 }],
{ duration: 260, easing: 'ease-out' }
[{ opacity: 0.35 }, { opacity: 0.5, offset: 0.42 }, { opacity: 1 }],
{ duration: 420, easing: 'ease-out' }
);
}