Chrome 111 の View Transitions API でリッチなページ間トランジションを実現する
Chrome 111 は CSS の色関数 や 三角関数の追加 など、目を引く更新が多いですが、中でも View Transitions API は、Web アプリの UX 向上という観点で、高いポテンシャルを秘めた API です。
この記事では、この View Transitions API について、実装例・実用例を交えて概説します。
View Transitions API とは
View Transitions API は、『画面の更新前後の異なる DOM 要素間のトランジションを、簡素な記述で実現』 する API です。
サポートブラウザ
2024/06/12 時点:
- Chrome: ✅ 111 以上 (https://developer.chrome.com/en/blog/new-in-chrome-111/)
- Edge: ✅ 111 以上
- Safari: ⏳ 18 以上 (https://www.webkit.org/blog/15443/news-from-wwdc24-webkit-in-safari-18-beta/)
- Firefox: ❌
つまりどういうこと?
モバイルのネイティブ App には、以下のように、 『ページ遷移が発生したとき、同じ要素がページをまたいで移動する』 ような、リッチな動きをするものが多くあります。選択された名前に注目してください。⏬
— https://developer.android.com/develop/ui/views/animations/transitions/start-activity
これを SPA (Single-page application) な Web ページで実現するのは、不可能ではないにしろ、かなり大変でした。💦
-
2 つの要素の移動アニメーションを、 JS/CSS で同時にコントロールしなければならない
- CSS アニメーション
- 表示位置の計算 (どこからどこまで移動するか)
- etc...
-
インタラクティブな要素のアクセシビリティの問題を、自分でハンドリングしないといけない
- キーボードフォーカス
- クリッカビリティ
- etc...
実際、配慮すべきことはかなり多いです。ライブラリを使用するにしても、そのライブラリ固有の事項が多く出てくることになるため、おそらく多くの人は気が進まないでしょう。😓
View Transitions API は、非常にシンプルな JS/CSS を使うだけで、こういった面倒事をブラウザ側で全部やってくれます。🙌
// 関数の実行前後の表示結果間でトランジションする
document.startViewTransition(() => {
// この中で DOM の更新処理を行う
});
.shared-image {
/* ページ遷移中に共有する要素に名前をつける */
view-transition-name: shared-image;
contain: paint;
}
また、トランジション中のアニメーションの制御も、従来からある CSS Animations や JS の Web Animation API を使用して行うことができるため、表現力も充分です。
MPA (Multi-pages application) への対応予定
このような 2 要素間のアニメーションを含むトランジション効果は、SPA はともかく、伝統的な MPA (Multi-page application) では、どう頑張っても現行の Web では実現不可能です。
しかし、 View Transitions API は、今後 MPA への対応も予定されている ため、今後は従来型のページでも、手軽にページ間のトランジション効果を使えるようになります。[1]
この記事では MPA の例は説明しませんが、気になる方は https://developer.chrome.com/docs/web-platform/view-transitions/cross-document?hl=ja を見てみてください。
サンプルで View Transitions API を理解する
具体的なサンプルを見ながら、 View Transitions API を理解していきましょう。デモページは、是非とも API が対応している Chrome 111 / Edge 111 以上のブラウザ でご覧ください。
単純な SPA
API の使用方法に焦点を合わせるため、非常に単純な SPA を作成してみます。以下の JSFiddle で、実際のデモをご覧いただけます。👇
<!DOCTYPE html>
<html>
<head>
<style>
.page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
padding: 20px;
}
.top {
background: #fff;
}
.sub1 {
background: #def;
}
.sub2 {
background: #fed;
}
</style>
</head>
<body>
<div class="page top" data-page="top">
<h1>Top page</h1>
<ul>
<li><a href="#sub1" data-to="sub1">Sub page 1</a></li>
<li><a href="#sub2" data-to="sub2">Sub page 2</a></li>
</ul>
</div>
<div class="page sub1" data-page="sub1" hidden>
<h2>Sub page 1</h2>
<p><a href="#top" data-to="top">Back</a></p>
</div>
<div class="page sub2" data-page="sub2" hidden>
<h2>Sub page 2</h2>
<p><a href="#top" data-to="top">Back</a></p>
</div>
<script>
document.querySelectorAll('a[data-to]').forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault()
const to = e.currentTarget.dataset.to
// ページを切り替える
document.querySelectorAll('[data-page]').forEach((page) => {
page.hidden = to !== page.dataset.page
})
})
})
</script>
</body>
</html>
トップページと、2つのサブページからなる、極めてシンプルな SPA です。このページ遷移に View Transitions API を適用していきましょう。
トランジション効果をつける
View Transitions API が提供する唯一の JavaScript 関数、 document.startViewTransition()
を使い、ページ切り替え時のトランジション効果をつけましょう。
document.startViewTransition()
には、引数として DOM を更新するための関数 (非同期関数でも OK) を渡します。ブラウザは、 関数の前後の表示結果に基づいて、トランジション効果を反映する のです。
// ページを切り替える
+document.startViewTransition(() => {
document.querySelectorAll('[data-page]').forEach((page) => {
page.hidden = to !== page.dataset.page
})
+})
単にページ切り替え部分を document.startViewTransition()
でラップしてあげただけですが、たったこれだけで、ページ切り替え時にクロスフェード効果がかかる ようになります。🪄
デモはこちら。👇 (要 Chrome/Edge 111 以上)
未対応ブラウザ向けの対応
未対応ブラウザを処理する場合、ラップした関数を使うと良いでしょう。 document.startViewTransition()
に対応していないブラウザでは、関数の内容をそのまま実行することで、トランジション無しで画面遷移を行うようにします。
function startViewTransition(callback) {
if (document.startViewTransition) {
document.startViewTransition(callback)
} else {
callback()
}
}
startViewTransition(() => {
document.querySelectorAll('[data-page]').forEach((page) => {
page.hidden = to !== page.dataset.page
})
)
この例は、あくまで簡易的な例です。 document.startViewTransition()
が返す ViewTransition
object などの API を全く考慮していないことにご注意ください。
トランジション効果の仕組み
トランジション効果は、その要素自体ではなく、::view-transition
をはじめとする、新しい一連の CSS 疑似要素の中で実現されています。トランジションの再生中は、 ::view-transition
とその子孫にあたる疑似要素が、画面上に覆いかぶさる形で表示されています。
View Transitions API によるトランジションが開始されると、ブラウザは、トランジション前後の画面の状態を、キャプチャ画像として撮影します。 📷
画面全体を示すグループは root
(::view-transition-group(root)
) と命名されており、 ::view-transition-image-old(root)
と ::view-transition-image-new(root)
が、キャプチャ画像に置換され、互いにアニメーションします。
デフォルトでは、 ::view-transition-image-old(*)
にフェードアウト効果、 ::view-transition-image-new(*)
にフェードイン効果が animation-name
CSS プロパティで定義されています。そのため、別の動きをさせたいのであれば、CSS でこれらのアニメーションを、別のものに変更すれば OK です。
例: スライドアニメーションに変更
@keyframes slide-to-left {
from {
left: 0%;
}
to {
left: -100%;
}
}
@keyframes slide-from-right {
from {
left: 100%;
}
to {
left: 0%;
}
}
::view-transition-old(root) {
animation-name: slide-to-left;
}
::view-transition-new(root) {
animation-name: slide-from-right;
}
デモはこちら。👇 (要 Chrome/Edge 111 以上)
2 要素間のトランジション
さて、この API の一番面白い部分である、『2 要素間のトランジション』 を実装しましょう。デモページには、トップページとサブページに、対になる猫の写真があります。この写真をページ遷移中にスムーズに動かしてみます。
とはいっても、やり方は超シンプルです。対となる画像に、新しい CSS プロパティ view-transition-name
を定義して、同じ名前をつけます。
.cat1 {
view-transition-name: cat1;
contain: paint; /* 現在の仕様ではこちらも必要 */
}
.cat2 {
view-transition-name: cat2;
contain: paint;
}
それぞれのクラスを、各ページの画像にアサインして、同じ名前の view-transition-name
のペアができるようにしたら、準備完了です。
<div class="page top" data-page="top">
<h1>Top page</h1>
<ul>
<li>
<a href="#sub1" data-to="sub1">
- <img src="https://placekitten.com/300/300" width="64" height="64" />
+ <img class="cat1" src="https://placekitten.com/300/300" width="64" height="64" />
Sub page 1
</a>
</li>
<li>
<a href="#sub2" data-to="sub2">
- <img src="https://placekitten.com/400/400" width="64" height="64" />
+ <img class="cat2" src="https://placekitten.com/400/400" width="64" height="64" />
Sub page 2
</a>
</li>
</ul>
</div>
<div class="page sub1" data-page="sub1" hidden>
<h2>Sub page 1</h2>
<p>
- <img src="https://placekitten.com/300/300" width="300" height="300" />
+ <img class="cat1" src="https://placekitten.com/300/300" width="300" height="300" />
</p>
<p><a href="#top" data-to="top">Back</a></p>
</div>
<div class="page sub2" data-page="sub2" hidden>
<h2>Sub page 2</h2>
<p>
- <img src="https://placekitten.com/400/400" width="300" height="300" />
+ <img class="cat2" src="https://placekitten.com/400/400" width="300" height="300" />
</p>
<p><a href="#top" data-to="top">Back</a></p>
</div>
たったこれだけで、ページ遷移時に、画像が各ページの大きさに合わせて拡大縮小してくれます。
デモはこちら。👇 (要 Chrome/Edge 111 以上)
『互いに異なる DOM 要素』 をアニメーションできるのがポイントです。実際、同じ画像でなくても OK ですし、全く別の要素 (<img>
と <div>
など) でも構いません。
Web アプリケーションという文脈においては、ヘッダーなど、遷移の前後で位置が変わらない要素を view-transition-name
でペアリングするのも良い使い道です。ページ遷移中も、ヘッダーが常に固定されているように見せることができます。
2 要素間トランジションの仕組み
view-transition-name
で個別に命名された要素がトランジションの前か後にある場合、::view-transition
疑似要素ツリーは以下のようになります。
::view-transition
┣ ::view-transition-group(root)
┃ ┗ ::view-transition-image-pair(root)
┃ ┣ ::view-transition-image-old(root)
┃ ┗ ::view-transition-image-new(root)
┗ ::view-transition-group(cat1)
┗ ::view-transition-image-pair(cat1)
┣ ::view-transition-image-old(cat1)
┗ ::view-transition-image-new(cat1)
::view-transition-group(cat1)
が、先ほどの view-transition-name: cat1
で定義された共有要素です。画面全体のトランジションである root
とは異なる別のグループが生成されることで、その要素には異なる動きが与えられる…… というカラクリです。
先ほどのように、指定された名前のグループに対して、 CSS で個別のアニメーションを設定することもできますし、 view-transition-group(*)
を使って、すべてのグループに対して一括で設定することもできます。
例: 2秒かけてゆっくりアニメーション
::view-transition-group(*) {
animation-duration: 2s;
}
デモはこちら。👇 (要 Chrome/Edge 111 以上)
以上が基本的な View transitions API の機能になります。記述そのものもかなりシンプルで、使いどころがハマれば、Web ページの体験をグッと向上させてくれそうです。
実用例
実装されたばかりということもあり、まだ著名なサービスでの実用例は見ておりません(タレコミお待ちしています)。手前味噌で申し訳ありませんが、筆者の例を1つ…
Marp
この記事の筆者が開発している Marp は、Markdown でプレゼンテーションスライドを作成するための、オープンソースのエコシステムです。
Marp では、前身の Shared Element Transitions API が発表された当初 (1年半前) から、プレゼンテーションのリッチなトランジション(画面遷移)を、他フレームワークやライブラリの依存なしで実現する手段 [2] として、この API に着目していました。
Chrome 111 での API 安定版リリースに先駆け、この機能も 安定版 になり、View Transitions API を活用した以下の機能を、Marp の各ツール (CLI、VS Code 拡張) で使用できるようになりました。
おわりに
Chrome 111 で実装された View Transitions API について、実装例と実用例を交えて概説しました。
document.startViewTransition()
が返す ViewTransition
object の内容など、 API の深い部分の説明などは今回省きました。より詳しく知りたい方は、Chrome Developer Blog (英語) で、View Transitions API のより具体的な使用例・サンプル・APIの詳細・他フレームワークとの統合例 (React, Vue, Svelte, Lit) などを見ることができます。
Chrome 111 で安定版になったとはいえ、MPA 対応も含めると API としてはまだ開発途上です。今後の実装予定などについては、以下の記事に記載されています。
また、設計仕様は、以下のドキュメントにまとまっています。
モバイルネイティブ App の機能から着想を得ただけあり、View Transitions API を上手く使えば、Web アプリの体験向上が期待できます。その分、使い方にはセンスが問われますが、導入自体はかなり容易なので、試行錯誤を繰り返し、最適解を見つけると良いかもしれません。
参考文献
- Smooth and simple transitions with the View Transitions API - Chrome Developers
- CSS View Transitions Module Level 1 (W3C Working Draft)
- View Transitions API - Web APIs | MDN
- WICG/view-transitions (GitHub)
- view-transitions/explainer.md at main · WICG/view-transitions
- HTTP 203 (サンプルアプリ)
-
かつて IE には、DHTML (Dynamic HTML) の一部として ページ遷移時のトランジション効果 が実装されていた時期がありましたので、初の事例というわけではありません。 ↩︎
-
Marp の出力は、"Less JavaScript" をかなり意識しています。 ↩︎
Discussion