View Transitions をMPAで試してみた（Experimental）

Webアプリケーションフロントエンドのアニメーションを簡単に作ることができる[View Transitions](https://github.com/WICG/view-transitions)をMPA(マルチページアプリケーション)で何パターンか試してみましたので、動作をまとめます。

# View Transitions について

Webアプリの画面の状態（DOM）が変化した時に変化内容に応じてアニメーションを行うための機能です。もっともシンプルなものは画面全体がクロスフェードするパターンです。以下の画像は View Transition のサンプルです。濃い背景色の `a.html` と明るい背景色の `b.html` をクロスフェードで切り替えています。

![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/d74cc216-e414-b7e1-1f92-8507cd66d786.gif)

View Transitionsは2023年4月現在ChromeやEdgeにのみ実装されています。**現状、標準で対応しているのはSPAのみ**であり、MPAでも使えるようにするためにはChromeの設定変更が必要になります。（[Can I use によるサポート状況](https://caniuse.com/?search=View%20Transition%20API)）

# MPA で View Transitions を使うことのメリット

View Transitions を導入することでリッチなアニメーションを追加できるようになります。例えば通販サイトにおいて、ユーザが商品一覧から商品を選んだ際、商品画像を拡大しながら次の画面に移ることで、自分がどの商品を選んだのか分かりやすくなります。
また、MPA は SPA と違い画面遷移前と遷移後でドキュメントが完全に分断されてしまうため従来の方法で画面遷移間のアニメーションを実装することはとても難しいのですが、View Transitions を使うとHTMLとCSSを少し変更するだけの簡単な修正でアニメーションを実装できます。
View Transitionsを使えば**レガシーなWebアプリであってもリッチな画面遷移アニメーションを実現することが容易**なため、ユーザ体験向上の方法の一つとして知っておいて損は無いかと思います。

# Chrome にて MPA でも View Transitions を使えるようにする

Chrome にて MPA でも View Transitions を使えるようにするためには chrome://flags を開き `viewTransition API for navigations` を `Enabled` に変更します。設定を変更したらブラウザの再起動が必要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/2eaa9cbf-5004-e048-34f2-619d6e18b875.png)

将来的にこの設定はデフォルトで `Enabled` になるものと思われます。

# MPA を View Transitions 化する最低限の記述

MPA で View Transition を使うためには以下の `meta` タグを**遷移前と遷移先両方のHTML**に記述する必要があります。また `content` 属性の値の通り、同じ[オリジン](https://developer.mozilla.org/ja/docs/Glossary/Origin)内の遷移である必要があります。
なお、この設定を行うだけでクロスフェードによる画面遷移が行われるようになります。

```html
<!DOCTYPE html>
<html>
  <head>
    ...
    <meta name="view-transition" content="same-origin" />
    ...
```

# View Transitions のテスト

では MPA で [View Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) をいくつかのパターンで試してみたいと思います。ソースコードは [StackBlitz](https://stackblitz.com/edit/web-platform-qabyqm) をご覧ください。
React や Vue.js のようなフレームワークの類は一切使用していません。

## 01 画面全体をクロスフェード

![2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/b81dce6c-db38-ed10-cca9-eab43e0a4112.gif)

先ほども説明した通り、以下の `meta` タグを HTML に追加するだけで画面全体のクロスフェードが発生します。
背景色だけではなくページ内のコンテンツも全てクロスフェードの対象です。

```html
<meta name="view-transition" content="same-origin" />
```

アニメーションが発生するタイミングはサンプルのようにハイパーリンクを押して遷移する時だけではなく、**ブラウザの戻る、進む機能を使った場合も同様にアニメーションが発生します。**

## 02 アニメーション時間を長くする

![3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/e9684cac-187a-e2cb-86c5-65157f9fc673.gif)

View Transitions のスタイルを設定することでアニメーション時間を長くすることができます。スタイルのサンプルは以下の通りです。

```css
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 3s;
}
```

- `view-transition-old` は遷移前のページのスクリーンショットの要素です。
- `view-transition-new` は遷移後のページの要素です。
- `root` は画面全体を示します。この部分を別の `view-transition-name` に変更することで特定の要素だけにスタイルの対象を絞ることができます。

なお、**アニメーションが完了するまでページの操作が完全にブロックされる**ようなので、過度に長い時間を設定しない方がいいと思います。（３秒は明らかに長すぎます。）

## 03 特定の要素をトゥイーンアニメーションする

![5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/6b20445e-db97-b778-5471-42caa08760ee.gif)


特定の要素に `view-transition-name` を指定することでトゥイーンアニメーションを実現することができます。サンプルのスタイルは以下の通りです。

```css
.target {
  view-transition-name: target;
}
```

遷移前と遷移後で同じ `view-transition-name` が割り当てられた要素がある場合にトゥイーンアニメーションが発生します。特定の要素に `view-transition-name` を設定するだけでもトゥイーンアニメーションを実現できるため、かなりお手軽な方法となっています。

なお `view-transition-name` はドキュメント内でユニークである必要があります。同じ `view-transition-name` が割り当てられた要素が遷移前と遷移後のいずれかに複数存在する場合はアニメーションが発生しません。（画面全体のクロスフェードすら発生しません。）

![6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/fe89c8b4-8f48-14ce-c2e9-93b57b902ee2.gif)

そのため複数の要素をアニメーションしたい場合は別々の `view-transition-name` を割り当てる必要があります。

![7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/527f2db8-6447-95a6-a946-63229fb37f7e.gif)

```css
.target {
  view-transition-name: target;
}

.target.sub {
  view-transition-name: target-sub;
}
```

このような仕様があるため、規模の大きなフロントエンドの場合 `view-transition-name` の割り当ては慎重に行う必要があります。

## 04 JavaScript で遅延生成した要素にアニメーションは行われない

JavaScript で生成した要素にアニメーションが行われる場合と行われない場合があるため注意が必要です。

まず以下の様に HTML の読み込みと同時に実行されるスクリプトで生成された場合はアニメーションが適用されます。

```html:b.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="view-transition" content="same-origin" />
    <title>Page B</title>
    <link href="../common.css" rel="stylesheet" />
    <link href="./common.css" rel="stylesheet" />
  </head>
  <body class="b">
    <div class="root">
      <div class="container">
        <div class="header">
          <div><a href="a.html">Page A</a></div>
          <div>Page B</div>
        </div>
        <div class="main">
          <div>View Transitions</div>
          <div>View Transitions</div>
          <div>View Transitions</div>
          <div>View Transitions</div>
          <div>View Transitions</div>
        </div>
      </div>
    </div>
    <script>
      const e = document.createElement('div');
      e.classList.add('target');
      document.querySelector('.main').appendChild(e);
    </script>
  </body>
</html>
```

![8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/f05453d7-3a72-55e3-4064-b707de77321d.gif)

ただし、上記の HTML のスクリプト部分を以下のコードのように遅延生成するように変更すると、画面全体はクロスフェードするものの、遅延生成対象である正方形の部品に対してトゥイーンアニメーションが適用されなくなります。（遅延生成後に元の画面に戻る場合はトゥイーンアニメーションします。）

```html
    <script>
      setTimeout(() => { // setTimeout で 100ms 後に部品を生成
        const e = document.createElement('div');
        e.classList.add('target');
        document.querySelector('.main').appendChild(e);
      }, 100);
    </script>
```

![9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/ef4a9428-bd78-57a2-39dd-3cc7c0d31fc9.gif)

正確な条件は調べ切れていないのですが、少なくとも現状は**ドキュメント読み込み時に対象の要素が存在していることがアニメーションの発生条件**になっていると思われます。そのため、MPAであってもJavaScriptによる遅延読み込みで画面を組み立てる方針のWebアプリの場合View Transitionsを簡単には適用できない可能性があります。そのためスケルトンUIを使って上手くごまかしながらView Transitionsを使うことも必要になると思います。

## 05 遷移前後で別々の種類の要素を使う

例えば遷移前は div 要素、遷移後は iframe 要素というように前後で別々の要素であっても問題なくアニメーションします。

![10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/8ac5785d-9b0a-3928-b969-c064b397871f.gif)

遷移前後の HTML の一部分をサンプルとして抜き出したものが以下のコードです。

```html:a.html
<div class="target"></div>
```

```html:b.html
<iframe
  class="target"
  width="560"
  height="315"
  src="https://www.youtube.com/embed/RH_ZJvUQsRw"
  title="YouTube video player"
  frameborder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
  allowfullscreen
></iframe>
```

## 06 画面全体を回転させながら遷移する

![11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/ba2d172a-c7e1-d641-e694-2ac78b3446c2.gif)

ちょっとお遊び的な所が大きいのですが、こういった派手なこともできますよということで紹介します。追加するスタイルは以下の通りです。

```css
@keyframes rotate {
  from {
    transform: rotateZ(0) scale(0);
  }
  to {
    transform: rotateZ(360deg) scale(1);
  }
}

::view-transition-new(root) {
  animation: 1s linear both rotate;
}
```

`::view-transition-*` に対して `animation` のスタイルを適用することで、画面全体や特定の要素に対してかなり細かいアニメーションを設定することができます。`::view-transition-new` の対象を特定の要素にした場合以下の様なアニメーションになります。

![12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2819924/ee9f4819-2b6f-1034-0123-7f2cbed5a221.gif)

こちらのスタイルは以下の通りです。

```css
.target {
  view-transition-name: target;
}

@keyframes rotate {
  from {
    transform: rotateZ(0) scale(0);
  }
  to {
    transform: rotateZ(360deg) scale(1);
  }
}

::view-transition-new(target) {
  animation: 1s linear both rotate;
}
```

# まとめ

View Transitions が追加されたことによりアニメーションが格段に追加しやすくなりました。View Transitions には[アクセシビリティの問題を一部解決できるというメリットもあったりする](https://github.com/WICG/view-transitions/blob/main/explainer.md#why-do-we-need-a-new-api-for-this)ため、今後は積極的に採用したほうがいいだろうと思っています。

MPAでもCSSを少し追加するだけで View Transitions に対応することができるため、レガシーなWebアプリに対して加えられるアクセントとしてはコストパフォーマンスが良い手段だと思います。（使いどころに注意しないとWeb画面が煩くなってしまうことには注意ですが。）

ただし、この記事を書いている時点ではまだ実験的な機能ですので実際に普及するのはまだ先の頃になるだろうと思います。いろいろなブラウザで View Transitions が正式に実装されれば、色々なWebアプリの見た目がガラッと変わると思いますので、その時がいまから楽しみです。