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:
co-authored by
Claude Opus 5
parent
9320092ebf
commit
2e15cdfddb
+32
-9
@@ -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' }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user