ข้ามไปเนื้อหาหลัก

Category: guide

Astro View Transitions — เปลี่ยนหน้าแบบ Smooth ด้วย ClientRouter

วิธีใช้ View Transitions API ใน Astro ผ่าน ClientRouter, transition:name สำหรับ shared elements, และการ handle events ที่ถูกต้อง

· อ่านประมาณ 2 นาที

สารบัญ

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-loadDOM พร้อม, script ทำงานinit components, Pagefind UI
astro:before-preparationก่อนโหลดหน้าใหม่cancel navigation, loading state
astro:after-swapDOM สลับเสร็จ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 อยู่แล้ว