fix(decnet_web/Layout): theme swap animation no longer flashes opposite mode
Growing the NEW theme layer from circle(0) outward leaves a one-frame gap where the new pseudo is fully opaque at full size (the default state) before the clip-path animation registers. Result: a flash of the destination theme right before the reveal starts. Inverted the layering and animation direction: - NEW theme snapshot sits on the bottom (z-index 0), static - OLD theme snapshot sits on top (z-index 1), shrinks via clip-path from circle(N) at click point down to circle(0) The new layer is now hidden behind the old one until the old shrinks away — no flash possible because the new layer was never visible before the animation. Same 520ms duration, same ease curve, same direction-of-travel from the user's POV (circle expanding from cursor).
This commit is contained in:
@@ -340,18 +340,26 @@ input:focus {
|
|||||||
/* ── Theme transition ───────────────────────────
|
/* ── Theme transition ───────────────────────────
|
||||||
* Disables the default cross-fade so the JS-driven
|
* Disables the default cross-fade so the JS-driven
|
||||||
* circle clip-path in useThemeToggle.ts owns the
|
* circle clip-path in useThemeToggle.ts owns the
|
||||||
* reveal entirely. The new theme grows from the
|
* reveal entirely.
|
||||||
* click point; the old theme stays put and is
|
*
|
||||||
* uncovered by the expanding circle. */
|
* Layering: the NEW theme snapshot sits behind, the
|
||||||
|
* OLD theme snapshot sits on top. The JS animation
|
||||||
|
* shrinks the old layer's clip-path from the
|
||||||
|
* viewport-covering circle down to circle(0) at the
|
||||||
|
* click point — burning away to reveal the new
|
||||||
|
* theme underneath. This avoids the one-frame flash
|
||||||
|
* you'd get by growing the new layer (where the
|
||||||
|
* default fully-opaque pseudo is visible for a tick
|
||||||
|
* before the clip-path animation registers). */
|
||||||
::view-transition-old(root),
|
::view-transition-old(root),
|
||||||
::view-transition-new(root) {
|
::view-transition-new(root) {
|
||||||
animation: none;
|
animation: none;
|
||||||
mix-blend-mode: normal;
|
mix-blend-mode: normal;
|
||||||
}
|
}
|
||||||
::view-transition-old(root) {
|
::view-transition-new(root) {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
::view-transition-new(root) {
|
::view-transition-old(root) {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,17 +70,22 @@ function animateSwap(next: Theme, x: number, y: number): void {
|
|||||||
Math.max(x, window.innerWidth - x),
|
Math.max(x, window.innerWidth - x),
|
||||||
Math.max(y, window.innerHeight - y),
|
Math.max(y, window.innerHeight - y),
|
||||||
);
|
);
|
||||||
|
/* Shrink the OLD layer (on top per index.css z-index rules)
|
||||||
|
* away from the click point, uncovering the NEW layer that
|
||||||
|
* sits behind. Going outside-in instead of inside-out avoids
|
||||||
|
* the one-frame flash where the default-opaque new pseudo
|
||||||
|
* would otherwise be visible before the clip-path registers. */
|
||||||
document.documentElement.animate(
|
document.documentElement.animate(
|
||||||
{
|
{
|
||||||
clipPath: [
|
clipPath: [
|
||||||
`circle(0px at ${x}px ${y}px)`,
|
|
||||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||||
|
`circle(0px at ${x}px ${y}px)`,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
duration: 520,
|
duration: 520,
|
||||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
pseudoElement: '::view-transition-new(root)',
|
pseudoElement: '::view-transition-old(root)',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}).catch(() => { /* user-cancelled or unsupported pseudo, ignore */ });
|
}).catch(() => { /* user-cancelled or unsupported pseudo, ignore */ });
|
||||||
|
|||||||
Reference in New Issue
Block a user