Category: guide
Astro View Transitions — เปลี่ยนหน้าแบบ Smooth ด้วย ClientRouter
วิธีใช้ View Transitions API ใน Astro ผ่าน ClientRouter, transition:name สำหรับ shared elements, และการ handle events ที่ถูกต้อง
สารบัญ
View Transitions คืออะไร
View Transitions API ของ browser ทำให้การเปลี่ยนหน้าดูลื่นขึ้น โดยแสดง animation ระหว่าง DOM state เก่าและใหม่ Astro ห่อ API นี้ไว้ใน ClientRouter ซึ่งทำงานข้ามทุก browser (ใช้ polyfill สำหรับ browser ที่ยังไม่รองรับ)
เปิดใช้งาน
---
// src/layouts/Layout.astro
import ClientRouter from 'astro/components/ClientRouter.astro';
---
<html>
<head>
<ClientRouter />
</head>
<body>...</body>
</html>
ใส่ <ClientRouter /> ใน <head> ครั้งเดียวใน Layout หลัก — ทุกหน้าที่ใช้ layout นี้จะได้ View Transitions อัตโนมัติ
Shared Element Transition ด้วย transition:name
ทำให้ element เดียวกัน “เคลื่อน” ข้ามหน้าได้ — เช่น Card title กลายเป็น h1 ของหน้า detail
<!-- src/components/Card.astro -->
<h3 transition:name={transitionName}>{title}</h3>
<!-- src/pages/projects/[slug].astro -->
<h1 transition:name={`page-title-${project.id}`}>{project.data.title}</h1>
กฎ: transition:name ต้องไม่ซ้ำกันในหน้าเดียวกัน ถ้าแสดง Card หลายใบให้ใช้ id เป็น suffix
Events ที่ต้องรู้
// ทำงานหลัง DOM ใหม่พร้อมแล้ว (เทียบได้กับ DOMContentLoaded ของหน้าใหม่)
document.addEventListener('astro:page-load', () => {
initComponents();
});
// ทำงานหลัง DOM เก่าถูกแทนที่แต่ก่อน animation เสร็จ
document.addEventListener('astro:after-swap', () => {
window.scrollTo({ top: 0, behavior: 'instant' });
});
| Event | เวลาที่ fire | ใช้สำหรับ |
|---|---|---|
astro:page-load | DOM พร้อม, script ทำงาน | init components, Pagefind UI |
astro:before-preparation | ก่อนโหลดหน้าใหม่ | cancel navigation, loading state |
astro:after-swap | DOM สลับเสร็จ | scroll reset, theme sync |
astro:after-preparation | โหลดเสร็จ ก่อน swap |
Scroll Reset
Astro View Transitions ไม่ reset scroll อัตโนมัติ ต้องทำเอง:
document.addEventListener('astro:after-swap', () => {
window.scrollTo({ top: 0, behavior: 'instant' });
});
ใช้ behavior: 'instant' ไม่ใช่ 'smooth' เพราะ scroll animation จะทับกับ page transition animation
Script ที่ต้อง re-init ทุกครั้ง
Script ที่ใช้ addEventListener('DOMContentLoaded', ...) จะไม่ทำงานหลัง navigation — ต้องเปลี่ยนเป็น astro:page-load:
// แบบเก่า — ไม่ทำงานหลัง View Transition
document.addEventListener('DOMContentLoaded', init);
// แบบถูก
document.addEventListener('astro:page-load', init);
กัน init ซ้ำ
Script บางตัวควร init ครั้งเดียวต่อ element เช่น Pagefind:
function initPagefind() {
const el = document.getElementById('search');
if (!el || el.dataset.pfInit) return; // ตรวจ flag ก่อน
el.dataset.pfInit = '1';
new PagefindUI({ element: '#search' });
}
document.addEventListener('astro:page-load', initPagefind);
ปิด Transition บาง Element
<!-- element นี้จะไม่มี transition animation -->
<div transition:animate="none">...</div>
<!-- ข้าม element นี้จาก transition ทั้งหมด -->
<nav transition:persist>...</nav>
transition:persist มีประโยชน์กับ audio/video player ที่ไม่อยากให้หยุดเมื่อเปลี่ยนหน้า
Tips สำหรับ Dark Mode
Dark mode toggle ที่อ่านจาก localStorage ต้องทำงานทั้งตอนโหลดครั้งแรกและหลัง transition:
// anti-FOUC: inline script ใน <head> — ทำงานก่อน render
(function() {
var s = localStorage.getItem('theme');
var dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = s || (dark ? 'dark' : 'light');
})();
// หลัง swap: ไม่ต้อง re-apply theme เพราะ <html> dataset ถูก persist อยู่แล้ว