0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【メモ】Astro View Transitionsを使ったページ間非同期遷移

Last updated at Posted at 2025-12-09

概要

AstroのView Transitions機能を使って、MPAで非同期的な遷移ができないか試してみました。

結論、astro:before-swapイベント内で使用できる関数swap()を使うことによって、自由度の高い遷移アニメーションを実現できそうです。

実装方針として、astro:before-swap内では、元ページのドキュメントdocumentと、遷移先ページのドキュメントe.newDocumentが取得できるので、それを手動で差し替えていく形になります。

新旧のドキュメントを同時に参照できるので、元ページと遷移先ページが共存する遷移アニメーションを作成できます。(参考:MIMEYOI

実装例

例えば、ページ遷移時に画面全体を覆うカバーを挟む場合、以下のような構造にして、カバーが覆ったタイミングでmain部分を差し替えます。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="generator" content={Astro.generator} />
    <title>{title}</title>
    <link rel="stylesheet" href="https://unpkg.com/lenis@1.3.15/dist/lenis.css" />
    <!-- ★忘れずに -->
    <ClientRouter /> 
  </head>

  <body>
    <!-- HeaderとTransitionCoverコンポーネントは差し替えない -->
    <Header />
    <TransitionCover />

    <!-- mainのみ差し替える -->
    <main>
      <slot />
    </main>
  </body>
</html>
document.addEventListener('astro:before-swap', (e) => {
    // スクロール位置を保存する
    sessionStorage.setItem('scrollPosition', window.scrollY.toString())
    
    e.swap = () => {
        const tl = gsap.timeline()
        tl.add(TransitionCover.Element!.animeIn()) // カバーイン
        tl.call(() => {
            // mainを差し替える
            const newMain = e.newDocument.querySelector('main')
            const oldMain = document.querySelector('main')
            if (newMain && oldMain) {
              oldMain.replaceWith(newMain)
              // ページタイトルの差し換え
              document.title = e.newDocument.title
            } else {
              document.body.replaceWith(e.newDocument.body)
            }
        })
        tl.add(TransitionCover.Element!.animeOut()) // カバーアウト
    }
}

document.addEventListener('astro:after-swap', (e) => {
    // スクロール位置を戻す
    const scrollPosition = sessionStorage.getItem('scrollPosition')
    if (scrollPosition) {
        window.scrollTo({ top: Number(scrollPosition), behavior: 'instant' })
        sessionStorage.removeItem('scrollPosition')
    }
})

swapに関するドキュメント(翻訳)

以下、Astro Building a custom swap functionのChatGPT翻訳になります。

カスタム swap 関数の構築

背景

  • Astro の View Transitions は、ナビゲーション時にページ間でスムーズなアニメーションを提供します。 (docs.astro.build)
  • デフォルトでは、Astro は組み込みの swap 実装を使って古いページの DOM を新しいページの DOM に置き換えます。ですが、必要に応じてこの置き換えロジックをカスタマイズできます。 (docs.astro.build)
  • そのために、astro:transitions/client モジュールが提供するユーティリティ関数群を活用します。 (docs.astro.build)

利用できるユーティリティ関数 (swapFunctions)

swapFunctions オブジェクトは以下のような関数を提供し、これらを組み合わせてカスタムな swap 実装を構築できます。 (docs.astro.build)

関数名 概要
deselectScripts(newDocument: Document) 新しいドキュメント中新たに読み込まれた <script> タグのうち、再実行不要なものにマークをつける(すでに現在のドキュメントにあるスクリプトなど) (docs.astro.build)
swapRootAttributes(newDocument: Document) <html> 要素(ドキュメントのルート)の属性を、新しいドキュメントの属性で置き換える。たとえば lang 属性や、内部で付与される data-astro-transition のような属性も対象。これを呼び出さないと、アニメーションや transition の方向指定が正しく動作しなくなる可能性がある。 (docs.astro.build)
swapHeadElements(newDocument: Document) <head> 内の要素を差分で更新。現在のドキュメントの <head> にあるうち、新しいページに存在しないものを削除し、新しいページの <head> にある要素を追加する。スタイルやスクリプトなど head 内リソースを適切に更新するために重要。 (docs.astro.build)
saveFocus() → returns () => void 現在フォーカスされている要素を記憶し、swap 後にフォーカスを復元するための関数を返す。ユーザー入力状態やフォーカスの保持に役立つ。 (docs.astro.build)
swapBodyElement(newBody: Element, oldBody: Element) 古いページの <body> を、新しいページの <body> に置き換える。ただし、transition:persist 指定された要素など、永続させたい要素があれば、それらを新しい DOM にマッチさせて維持する。 (docs.astro.build)

カスタム swap の基本テンプレート

たとえば、Astro のデフォルトの swap 動作を再現するカスタム実装は、以下のように書くことができます。 (docs.astro.build)

import { swapFunctions } from "astro:transitions/client";

function mySwap(doc: Document) {
    swapFunctions.deselectScripts(doc);
    swapFunctions.swapRootAttributes(doc);
    swapFunctions.swapHeadElements(doc);
    const restoreFocusFunction = swapFunctions.saveFocus();
    swapFunctions.swapBodyElement(doc.body, document.body);
    restoreFocusFunction();
}

document.addEventListener("astro:before-swap", (event) => {
    event.swap = () => mySwap(event.newDocument);
});

上記のように、astro:before-swap イベントで event.swap に自前の関数を割り当てることで、デフォルト置き換えロジックをカスタマイズできます。 (docs.astro.build)

このテンプレートを土台にして、例えば次のようなカスタム処理を挟むことも可能です:

  • head 要素の差分更新後に特定のスクリプトの実行制御を追加
  • body の置き換え前後で特定の DOM の変換/クリーンアップ
  • フォーカス復元の前に任意の UI 更新(スクロール、スタイル、状態保存… など)

注意点と活用のコツ

  • swapRootAttributes を呼び出すことは非常に重要です。これを怠ると、transition の方向や内部的な属性 (data-astro-transition など) が正しく反映されず、アニメーションやスタイルが壊れる可能性があります。 (docs.astro.build)
  • saveFocusrestoreFocusFunction を使うことで、ページ遷移前にフォーカスがあった要素があれば、遷移後も同じ要素にフォーカスを戻せます。アクセシビリティやキーボード操作の観点から便利です。
  • transition:persist を使って「永続させたい要素」を指定している場合は、swapBodyElement によってそれら要素の移行が適切に処理されます。
  • 必要に応じて、上記テンプレートの各ステップ間に「独自処理(カスタムロジック)」を挟むことで、非常に柔軟なページ遷移体験を構築できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?