轉場的本質
頁面轉場要解決的問題只有一個:在舊頁消失與新頁出現之間,填補那段空窗。空窗處理得差,使用者看到白屏閃爍;處理得好,使用者感受到的是連續的空間。
舊頁 → 離開動畫 → 數據載入 → 內容替換 → 進場動畫 → 新頁 onNavigate:官方掛載點
SvelteKit 提供 onNavigate,回傳的 Promise 會讓框架等待你再完成導航:
import { onNavigate } from '$app/navigation';
onNavigate((navigation) => {
if (!document.startViewTransition) return;
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
}); 這是 View Transitions API 的用法。但如果你想完全控制動畫 — 遮罩、時序、緩動 — GSAP 是更好的選擇:
onNavigate(async ({ from, to }) => {
if (!from || !to || from.url.pathname === to.url.pathname) return;
await pageLeave(); // GSAP timeline:舊頁淡出 + 遮罩進場
}); 常見錯誤
用 beforeNavigate + cancel + goto
// ❌ 有狀態卡死風險
beforeNavigate(({ cancel, to }) => {
if (isTransitioning) return;
cancel();
animateOut().then(() => goto(to.url));
}); goto 失敗或被中斷時,isTransitioning 永遠不會被重置,導航徹底卡死。
動畫沒有超時兜底
動畫因為任何原因沒有 resolve,導航就永遠阻塞。一律加上:
const MAX_LEAVE = 1.2;
gsap.delayedCall(MAX_LEAVE, () => tl.progress(1)); 遮罩轉場
最經典的 Awwwards 風格轉場是一塊全屏遮罩(curtain):
gsap
.timeline()
.to('.overlay', { scaleY: 1, transformOrigin: 'bottom', duration: 0.5, ease: 'power4.inOut' })
.to('.overlay', { scaleY: 0, transformOrigin: 'top', duration: 0.5, ease: 'power4.out' }); 遮罩蓋上時替換內容,掀開時新頁已就緒 — 使用者永遠看不到空窗。
小結
- 用
onNavigate,不要用cancel + goto - 所有轉場動畫都要有超時兜底
prefers-reduced-motion時直接跳過動畫- 轉場前後記得清理與重建
ScrollTrigger