Blog

<Astro3.0>View Transition APIをサポート!ページ間のトランジションが簡単実装可能に!

2023年8月末Astro3.0が登場したので、その目玉であるView Transitionsに焦点をあてて実装テストをしました。

View Transition APIは、ページ遷移の時に、スムーズで連続性のあるアニメーションを実装できるというものです。
ページ遷移のアニメーションについて、それまではSPAフレームワークでコンポーネントを差し替えるような実装では可能でした。が、静的HTMLのページ移動で前後の状態を加味したシームレスなアニメーションを実装するのはかなり困難でした。

今回Astro3でサポートされたView Transitionsでは、ごくシンプルな実装で簡単に View Transition APIの動作を取り入れられます。

以下は、Astroの公式「View Transitions」のページを参考に、自分なりにいろいろ試してみたものです。

https://docs.astro.build/en/guides/view-transitions/
公式もわかりやすいので、そちらも参考にしてください。

📖astroバージョン3.0.7時点の記事です。

View Transition を有効にする

---
import {ViewTransitions} from 'astro:transitions';
---

<head>
	<ViewTransitions/>
<head>

ViewTransitionsをimportし、headタグ内に<ViewTransitions/>を記述するだけです(headのこの部分は共通コンポーネントにしている前提)。
これでサイト全体でView Transitionsが有効になり、クライアントサイドルーティングになります。

この状態で、ページ切り替えの際にふわっとフェードで切り替わるようになりました。

View Transitionになってほしくないリンクはdata-astro-reload属性を付与する

例えば、アンカーリンクに<a>要素を使っていてスムーススクロールを実装している場合、View Transitionが邪魔をしてうまくいきません。
また、PDFファイルへのリンクなどもクライアントサイドルーティングは必要ありません。

それらの<a>要素にはdata-astro-reload属性を付与します。

<a href="/quarterly-earnings.pdf" data-astro-reload>Download PDF</a>

これで、このリンクだけ View Transitionsをオフにできます。

ページ遷移時のアニメーションを実装する

アニメーションの種類

Layout.astroのhtml要素にtransition:animate属性を指定するだけで、デフォルトのfadeから変更することが可能です。

<html lang="ja" transition:animate="slide">

html要素にtransition:animateを指定するとページ全体がアニメーションしますが、他の要素に指定することもできます。
例えばh1transition:animate="slide"を指定すると、h1要素がスライドアニメーションします。

デフォルトではページ切り替え時のアニメーションはfadeになっていますが、下記の種類があります。

- `fade`
(デフォルト): 独自のクロスフェードアニメーション。古いコンテンツがフェードアウトし、新しいコンテンツがフェードインします。
- `initial`
: Astro の独自のクロスフェード アニメーションをオプトアウトし、ブラウザのデフォルトのスタイルを使用します。
- `slide`
: 古いコンテンツが左にスライドアウトし、新しいコンテンツが右からスライドインするアニメーション。逆方向のナビゲーションでは、アニメーションは逆になります。
- `none<html>`
: ブラウザのデフォルトのアニメーションを無効にします。ページの要素で使用して、ページ上のすべての要素のデフォルトのフェードを無効にします。

https://docs.astro.build/en/guides/view-transitions/#built-in-animation-directives

このうちエフェクトっぽいのはfadeとslideだけなのでどちらかを選ぶことになりそうですが、個人的にはslideは若干大袈裟な動きに感じるのでfadeが無難かもしれません。

トランジションのカスタマイズ

パラメータを型にあわせてオブジェクトを作り、transition:animateに指定すると、アニメーションのカスタマイズができます。
ちなみに、slideとfadeはこのパラメータのようです。

fadeかslideの、durationを上書きするのみなら下記のような感じで簡単にできます。

---
import { slide } from 'astro:transitions';
---

<header transition:animate={slide({ duration: '2s' })}>

現状はnameやdelayはこのやり方ではオーバーライドできないようです。

オブジェクトを作る場合は、nameをAstroの組み込みのキーフレームに合わせ、パラメータを上書きしていきます。


---
import type { TransitionDirectionalAnimations } from "astro";

const contentAnim: TransitionDirectionalAnimations = {
	forwards: {
		old: [
			{
				name: "astroFadeOut",
				duration: "1s",
				easing: "linear",
				delay: "0s",
				fillMode: "both"
			},
			{
				name: 'astroSlideToLeft',
				duration: "1s",
				easing: 'cubic-bezier(0.76, 0, 0.24, 1)',
				delay: "0s",
				fillMode: 'both',
			},
		],
		new: [
			{
				name: "astroFadeIn",
				duration: "1s",
				easing: "linear",
				delay: "0s",
				fillMode: "both"
			},
			{
				name: "astroSlideFromRight",
				duration: "1s",
				easing: 'cubic-bezier(0.76, 0, 0.24, 1)',
				delay: "0s",
				fillMode: "both"
			}
		]
	},
	backwards: {
		old: [{ name: "astroFadeOut" }, { name: "astroSlideToRight" }],
		new: [{ name: "astroFadeIn" }, { name: "astroSlideFromLeft" }]
	}
};
---

<html lang="ja" transition:animate={contentAnim}>

上記例では、1sかけて左から右にページがスライドします。

TransitionDirectionalAnimationsの型をimportして使用することができます。

公式の例では、nameを"fadeIn"などにした例が紹介されていましたが、このname(astroFadeOutなど)にしないとアニメーションしませんでした…。nameは「// The name of the keyframe」と公式ドキュメントにもかいてあるのですが、nameをmySlideFromRightなどにしてみて、これにcssのkeyframesを設定してみても適用されませんでした。詳細は不明です。

keyframesの上書きはできない?

上でも触れましたが、個人的にデフォルトのslideは、画面幅100%分動くのが大袈裟な感じがして気に入らず、どうにかならないかと検討しました。
下記のような感じにしたいのですが、結果的にkeyframesはうまく上書きできませんでした。方法があれば知りたいです。

@keyframes astroSlideFromRight {
	from {
		transform: translateX(20px);
	}
}
@keyframes astroSlideToLeft {
	to {
		transform: translateX(-20px);
	}
}

transition:name で要素を関連付け

View Transition API で一番よく見かけるトランジションが、ページ移動したら次のページの同じ画像の位置に画像がぴょんと飛んでいき スっとはまるような動きだと思います。

Astroはなんと、旧ページと新ページを見て対応する要素を判断するそうです。

Astroは、旧ページと新ページの両方で見つかった対応する要素に、共有された一意のview-transition-nameを自動的に割り当てます。この一致する要素のペアは、要素のタイプとDOM内の位置の両方によって推測されます。

https://docs.astro.build/en/guides/view-transitions/#transition-directives

…が、私が作ったテストサイトは、要素のタイプとDOM内の位置が悪かったのか、Astroの自動検出には引っ掛からなかったようでした。

そんな時でも手動で関連づけられるのがtransition:nameです。
Astroのデフォルトの要素一致をオーバーライドし、DOM要素のペアを関連付けることができます。

一覧ページのサムネイルと、詳細ページの同じ画像にそれぞれtransition:nameを付与します。

<img src="/img/shop/mv-shop.webp" alt="" transition:name="shopMv">

これでシームレスな遷移が実装できます。

ちなみに現時点で私の環境では、この動き(次のページの同じ画像の位置まで移動する動き)はChromeでしか確認できませんでした。

🔽テストしたFirefoxとsafariのバージョン

  • Firefox 117.0
  • safari 15.3

サポートしていないブラウザやバージョンの場合、フォールバックサポートが提供されています。
基本的には、ぬっと出てくるアニメーションがなく、html要素に指定したtransition:animateのフェードでページが切り替わるだけになったのですが、それでも十分だと思いました。

また、transition:nameの別の使いどころとして、サイト全体をslide(html要素に transition:animate="slide")にした場合に、固定ヘッダーを動かしたくない場合なんかも使えそうでした。(あまりサイト全体をslideにしたりしないと思いますが)

Header.astroコンポーネントを作っていたら、それにtransition:name="header"を付与します。

<header transition:name="header">

そうすると、外側はスライドしても、ヘッダーは位置据え置きのまま次のページに遷移します。外側の要素がスライダーで、一部動かしたくない要素がある場合に使えそうな気がしました。

transition:persist で状態の維持

transition:persistディレクティブを使用すると、ページ遷移の時に、コンポーネントとHTML要素を (置き換えるのではなく) 保持できます。
たとえば、前のページでvideoが再生中で3秒の時点でページ遷移した場合も、次のページの同じvideoでは最初から再生されるのではなく、再生が続いています。

<video src="/movie/animal.mp4" muted autoplay loop transition:persist="media-player"></video>

一昔前は考えられなかったことがこんな簡単に…。驚きました。

ページナビゲーションのイベント

イベント astro:page-load

ViewTransitionsを有効にしていると、下記のようなwindowのイベントリスナーのloadが効かなくなります。

window.addEventListener("load", function () {
  document.body.classList.add("is-after-load");
});

理由はおそらくここに書かれている「クライアント側のナビゲーションプロセス」にあります。

<ViewTransitions />を使用する場合はSPAモードになり、スクリプトは新しいページに存在すればそのまま残すようになっているからだと思われます。
他にもheadの内容が入れ替えられる時にスタイルシートのDOMノードを残すなど、クライアント側ナビゲーションを生成するための処理が公式に書いてありました。

ViewTransitionsを有効にした状態でloadイベントを起こすには、page-loadイベントを使います。

新しいページがユーザに表示され、スタイルとスクリプトがロードされた後、ページナビゲーションの終了時に発生するイベント。ドキュメント上でこのイベントをリッスンすることができます。
<ViewTransitions />コンポーネントは、最初のページナビゲーション(MPA)と、その後のナビゲーション(前方または後方)の両方でこのイベントを発生させます。
このイベントを使用して、すべてのページ・ナビゲーションでコードを実行することも、一度だけ実行することもできます

https://docs.astro.build/en/guides/view-transitions/#astropage-load
const transitionPageLoad = ()=> {
  document.addEventListener(
    "astro:page-load",
    () => {
      // This only runs once.
      document.body.classList.add("is-loaded");
    }
    { once: true }
  );
}
transitionPageLoad()

イベント astro:after-swap

新しいページが古いページを置き換えた直後に発生するイベントです。documentでこのイベントをリッスンすることができます。

例えば、ダークモードを実装している場合に、状態を復元するのに便利だということです。
https://docs.astro.build/en/guides/view-transitions/#astroafter-swap

アニメーションが苦手なユーザーのためのprefers-reduced-motion

prefers-reduced-motionCSSメディア機能は、アニメーションを気持ち悪いと感じるユーザーが、自身のデバイスに動きを減らすよう設定をした時に、それを検出するというものです。
Astroの<ViewTransitions />には、その場合にすべてのView Transitionアニメーションを無効にするCSSメディアクエリが含まれています。

気になるところ

ビルド後、animationプロパティが大量に記述されたstyleタグがheadに差し込まれます。
自分でデプロイする案件なら問題ないのですが、htmlファイルを納品するような案件だと若干気になります。
設定で別ファイルになるなど選べたらよいなと思ったのですが見つけることができませんでした。

まとめ

View Transition API はなんとなくチェックしていましたが、実務に取り入れるにはまだちょっとハードルが高いと感じていました

…が、普段使いのAstroがサポートしてくれたので、これからいろいろと試してみようと思います。
かなり嬉しいアップデートでした。

余談ですが、Astro3.0になって、ビルド後にHTMLが最適化された状態になるのは狼狽えました。
HTMLをそのまま納品する案件も結構あり、デフォルトで最適化は困るので、astro.config.mjsに下記を追加することで事なきを得ました。焦った…

compressHTML: false

参考

Astro | View Transitions
https://docs.astro.build/en/guides/view-transitions/#astroafter-swap

Astro3.0
https://astro.build/blog/astro-3/

Astro View Transitions - Chrome Developers
https://developer.chrome.com/blog/astro-view-transitions/

Smooth and simple transitions with the View Transitions API - Chrome Developers
https://developer.chrome.com/docs/web-platform/view-transitions/

おすすめの記事 recommend blog

新着 new blog

github