fix(theme): VT 期间冻结 CSS 过渡,让圆形展开可见
body 有 transition: background-color .3s,VT 在 toggle dark class 后下一帧拍 NEW 快照时背景色才走了 ~5%,新旧快照几乎一样, 圆形展开看不出颜色差。修复:回调里加 vt-freeze 类禁用所有 过渡,让 NEW 快照立即拿到新主题配色;动画结束(finished)后 移除。补 1 个测试覆盖 freeze 生命周期。
This commit is contained in:
parent
1e78c7f0f9
commit
c1781d7831
@ -1,6 +1,17 @@
|
||||
/* ========== 主题切换圆形展开(View Transitions API) ========== */
|
||||
/* 取消默认交叉淡入淡出,由 clip-path: circle() 接管新快照的展开。 */
|
||||
|
||||
/* VT 回调期间临时冻结所有 CSS 过渡。
|
||||
原因:body 有 transition: background-color 0.3s(input.css),
|
||||
toggle dark class 后真实渲染的背景色要 0.3s 才过渡到新主题。
|
||||
而 VT 在 toggle 后的下一帧(~16ms)拍 NEW 快照——此时背景色才走了 ~5%,
|
||||
新旧两张快照几乎一样,圆形展开就看不出颜色差。
|
||||
冻结过渡让 NEW 快照立即拿到新主题配色,圆形才有可见的颜色对比。 */
|
||||
html.vt-freeze,
|
||||
html.vt-freeze * {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* 取消默认交叉淡入淡出,由 clip-path: circle() 接管新快照的展开。 */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
@ -20,3 +31,4 @@
|
||||
clip-path: circle(var(--tt-max-r, 100vmax) at var(--tt-x, 0px) var(--tt-y, 0px));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,11 +10,13 @@ import './index';
|
||||
describe('startThemeTransition', () => {
|
||||
beforeEach(() => {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.remove('vt-freeze');
|
||||
document.documentElement.style.cssText = '';
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
afterEach(() => {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.remove('vt-freeze');
|
||||
document.documentElement.style.cssText = '';
|
||||
});
|
||||
|
||||
@ -66,6 +68,32 @@ describe('startThemeTransition', () => {
|
||||
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
||||
cbRef.cb!();
|
||||
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
||||
// 回调里同时冻结 CSS 过渡,让 NEW 快照立即反映新主题配色
|
||||
expect(document.documentElement.classList.contains('vt-freeze')).toBe(true);
|
||||
|
||||
delete (document as unknown as { startViewTransition?: unknown }).startViewTransition;
|
||||
});
|
||||
|
||||
it('主路径:vt.finished 结束后移除 vt-freeze 并复位重入标志', async () => {
|
||||
const finishedPromise = Promise.resolve();
|
||||
const startVT = vi.fn((cb: () => void) => {
|
||||
cb();
|
||||
return { finished: finishedPromise, skipTransition: () => {} };
|
||||
});
|
||||
Object.defineProperty(document, 'startViewTransition', {
|
||||
value: startVT,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
window.__startThemeTransition(0, 0, true);
|
||||
// 回调执行后 freeze 已加
|
||||
expect(document.documentElement.classList.contains('vt-freeze')).toBe(true);
|
||||
|
||||
await finishedPromise;
|
||||
// 微任务:.finally 在 finished resolve 后于微任务阶段执行,等一轮
|
||||
await Promise.resolve();
|
||||
expect(document.documentElement.classList.contains('vt-freeze')).toBe(false);
|
||||
|
||||
delete (document as unknown as { startViewTransition?: unknown }).startViewTransition;
|
||||
});
|
||||
|
||||
@ -65,15 +65,19 @@ export function startThemeTransition(x: number, y: number, isDark: boolean): voi
|
||||
html.style.setProperty('--tt-y', `${y}px`);
|
||||
html.style.setProperty('--tt-max-r', `${maxCornerDistance(x, y)}px`);
|
||||
|
||||
// VT 回调里:先冻结所有 CSS 过渡(让 NEW 快照立即拿到新主题配色,
|
||||
// 不被 body 的 background-color .3s 过渡拖慢),再 toggle dark class。
|
||||
const vt = document.startViewTransition(() => {
|
||||
html.classList.add('vt-freeze');
|
||||
applyDarkClass(isDark);
|
||||
});
|
||||
|
||||
// vt.finished 在 skipTransition 或页面跳转时会 reject(属预期),用 .catch 吞掉
|
||||
// 以避免 unhandled rejection 刷控制台;无论 resolve/reject 都复位重入标志。
|
||||
// 以避免 unhandled rejection 刷控制台;无论 resolve/reject 都复位重入标志与冻结。
|
||||
vt.finished
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
html.classList.remove('vt-freeze');
|
||||
transitioning = false;
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user