🛫

Chrome 111 の View Transitions API でリッチなページ間トランジションを実現する

2023/03/08に公開

Chrome 111 は CSS の色関数三角関数の追加 など、目を引く更新が多いですが、中でも View Transitions API は、Web アプリの UX 向上という観点で、高いポテンシャルを秘めた API です。

https://developer.chrome.com/en/blog/new-in-chrome-111/

この記事では、この View Transitions API について、実装例・実用例を交えて概説します。

View Transitions API とは

https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

View Transitions API は、『画面の更新前後の異なる DOM 要素間のトランジションを、簡素な記述で実現』 する API です。

PC および Android の Chrome 111 以上、または Edge 111 以上 で使用できます。

つまりどういうこと?

モバイルのネイティブ App には、以下のように、 『ページ遷移が発生したとき、同じ要素がページをまたいで移動する』 ような、リッチな動きをするものが多くあります。選択された名前に注目してください。⏬

リッチなページ遷移のモバイルネイティブ 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]

<!-- 同一オリジン間で View Transitions を有効にする -->
<meta name="view-transition" content="same-origin" />

Chrome 111 時点で View Transitions API は SPA 向けの API が安定版となっており、MPA 向けの API は about:flags で実験的に提供されています。 (chrome://flags/#view-transition-on-navigation)

この記事では MPA の例は説明しませんが、気になる方は https://github.com/WICG/view-transitions/blob/main/explainer.md#cross-document-same-origin-transitions を見てみてください。

サンプルで 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>

単純なSPA

トップページと、2つのサブページからなる、極めてシンプルな SPA です。このページ遷移に View Transitions API を適用していきましょう。

トランジション効果をつける

View Transitions API が提供する唯一の JavaScript 関数、 document.startViewTransition() を使い、ページ切り替え時のトランジション効果をつけましょう。

https://developer.mozilla.org/en-US/docs/Web/API/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 を定義して、同じ名前をつけます。

https://developer.mozilla.org/en-US/docs/Web/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>

たったこれだけで、ページ遷移時に、画像が各ページの大きさに合わせて拡大縮小してくれます。

2 要素間のトランジション

デモはこちら。👇 (要 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;
}

2秒かけてゆっくりアニメーション

デモはこちら。👇 (要 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 の各ツール (CLIVS Code 拡張) で使用できるようになりました。

https://twitter.com/y_hatt/status/1630977276258181120

おわりに

Chrome 111 で実装された View Transitions API について、実装例と実用例を交えて概説しました。

document.startViewTransition() が返す ViewTransition object の内容など、 API の深い部分の説明などは今回省きました。より詳しく知りたい方は、Chrome Developer Blog (英語) で、View Transitions API のより具体的な使用例・サンプル・APIの詳細・他フレームワークとの統合例 (React, Vue, Svelte, Lit) などを見ることができます。

https://developer.chrome.com/docs/web-platform/view-transitions/

Chrome 111 で安定版になったとはいえ、MPA 対応も含めると API としてはまだ開発途上です。今後の実装予定などについては、以下の記事に記載されています。

https://developer.chrome.com/blog/spa-view-transitions-land/

また、設計仕様は、以下のドキュメントにまとまっています。

モバイルネイティブ App の機能から着想を得ただけあり、View Transitions API を上手く使えば、Web アプリの体験向上が期待できます。その分、使い方にはセンスが問われますが、導入自体はかなり容易なので、試行錯誤を繰り返し、最適解を見つけると良いかもしれません。

参考文献

脚注
  1. かつて IE には、DHTML (Dynamic HTML) の一部として ページ遷移時のトランジション効果 が実装されていた時期がありましたので、初の事例というわけではありません。 ↩︎

  2. Marp の出力は、"Less JavaScript" をかなり意識しています。 ↩︎

Discussion