小さく始めるView Transitions API

2024/07/10に公開

0.概要

2023年3月にリリースされたView Transitions API。トランジション時のリッチなアニメーションを簡単に実装できる手法として、たびたび話題になってます。

まだChromeしか対応していませんが、いざサポートが拡大した時に取り残されないよう、すこーしだけ導入してみました。

対象読者

  • まだView Transitions APIを触ったことがない人
  • 触ったことあるけど、実際に導入する手順がぱっと思い浮かばない人

※ 本記事のコードはReactTypeScriptを前提に書いております。

今回作ったリポジトリ

https://github.com/hiroshi-kato/view-transitions-demo

サイト

https://view-transitions-demo-gamma.vercel.app/

Next.jsのブログテンプレートを使って各所にView Transitions APIを適用してみました。
トップページの画像と記事詳細からトップに戻るボタンに適用済み、記事タイトルには適用してないので、ぜひ比べてみてください。

1.View Transitions APIとは?

View Transitions APIは、ページやUIの切り替え時にスムーズなトランジション効果を提供する新しいAPIです。これを使うことで、ユーザー体験を向上させることができます。具体的には、ページ遷移やコンテンツの変更時にフェードイン・フェードアウトなどのアニメーション効果を簡単に適用できます。このAPIは、従来の手動でのアニメーション実装よりも簡単で一貫性のある方法を提供します。

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

2.下準備

2-1. 型ファイルの作成

View Transitions APIはまだChromeしかサポートしていないため、TypeScriptの型に含まれていません。
サポートブラウザが2つ以上になると追加されるそうです。
↓参考
https://github.com/microsoft/TypeScript-DOM-lib-generator

ちなみにライブラリが型ファイルを用意してくれてる場合もあるので、 どこかでdocument.startViewTransition と書いてみて型エラーになるか確認してみましょう。型エラーにならなかったら本工程は不要です。

(2024/07/10時点だとReact Routerは用意してくれていますが、Next.js は無かったです)

// global.d.ts
export interface ViewTransition {
  ready: Promise<void>;
  finished: Promise<void>;
  updateCallbackDone: Promise<void>;
  skipTransition: () => undefined;
}

declare global {
  interface Document {
    startViewTransition?: (cb: () => Promise<void> | void) => ViewTransition;
  }
}

作成したファイルをtsconfig.jsonincludeに追加します。

{
  "compilerOptions": {
		// 省略
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    "src/types/global.d.ts"  // ここ
  ],
  "exclude": ["node_modules"]
}

2-2. startViewTransitionのラッパー関数を作成

今回はView Transitions APIの中核となるdocument.startViewTransitionを素朴に使ってきます。
ただし、このAPIはまだすべてのブラウザでサポートされているわけではないため、FirefoxやSafariといった未対応ブラウザでエラーにならないようにラッパー関数を作成します。

// viewTransitionAPIラッパー関数
export const startViewTransition = (callback) => {
  if ('startViewTransition' in document) {
    document.startViewTransition(callback);
  } else {
    // 未対応ブラウザのためのフォールバック処理
    callback();
  }
};

https://developer.mozilla.org/ja/docs/Web/API/Document/startViewTransition

https://caniuse.com/?search=startViewTransition

3. startViewTransitionを使ってみる

3-1.DOMを更新する関数に適用する

次に、DOMの変更があるところでstartViewTransition関数を使います。例えば、React Routerを使ったhistory.pushなどの操作に適用してみます。

import { useHistory } from '`React Router`-dom';

const MyComponent = () => {
  const history = useHistory();

  const handleNavigation = () => {
    startTransition(() => {
      history.push('/next-page');
    });
  };

  return (
    <button onClick={handleNavigation}>次のページへ</button>
  );
};

3-2. Linkコンポーネントに適用する

onClickなどのイベントハンドラーに適用する場合は3-1だけで十分ですが、せっかくなのでReact RouterやNext.jsでよく使うLinkコンポーネントにも適用してみます。

まずはRouterを拡張します。

// useViewTransitionRouter.ts

import { useRouter } from 'next/navigation';
import { startViewTransition } from './startViewTransition';

export const useViewTransitionRouter = () => {
  const router = useRouter();

  return {
    ...router,
    push: (href: string) => {
      startViewTransition(() => router.push(href));
    },
  };
};

次にLinkコンポーネントを拡張します。

// ViewTransitionLink.ts

import { useViewTransitionRouter } from '@/lib/useViewTransitionRouter';
import NextLink from 'next/link';

export const ViewTransitionLink = ({
  href,
  ...props
}: React.ComponentProps<typeof NextLink>) => {
  const router = useViewTransitionRouter();

  const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    e.preventDefault();
    router.push(href.toString());
  };

  return <NextLink href={href} {...props} onClick={handleClick} />;
};

任意の場所に適用してみます。

// cover-iamge.tsx

'use client';

import cn from 'classnames';
import Image from 'next/image';
import { ViewTransitionLink as Link } from './view-transition-link';

type Props = {
  title: string;
  src: string;
  slug?: string;
};

const CoverImage = ({ title, src, slug }: Props) => {
  const image = (
    <Image
      src={src}
      alt={`Cover Image for ${title}`}
      className={cn('shadow-sm w-full', {
        'hover:shadow-lg transition-shadow duration-200': slug,
      })}
      width={1300}
      height={630}
    />
  );
  return (
    <div className='sm:mx-0'>
      {slug ? (
        <Link href={`/posts/${slug}`} aria-label={title}>
          {image}
        </Link>
      ) : (
        image
      )}
    </div>
  );
};

export default CoverImage;

これでLinkコンポーネントでもstartViewTransitionが適用されました。

ViewTransitionAPIのデフォルトのアニメーションはフェードイン・フェードアウトです。これにより、ページやUIの切り替えがスムーズになります。

導入はこれで終わり。晴れてView Transitions APIデビューです。

4. まとめ

最初は嬉しくなって色んなところにstartViewTransitionを入れたのですが、過剰だったり効果が薄かったりするケースも多く、結局導入は数か所にとどまりました。

アニメーションが苦手な方もいるので、prefers-reduced-motion も適用しつつ、使いすぎには注意しましょう。

prefers-reduced-motion - CSS: カスケーディングスタイルシート | MDN

TimeTree Tech Blog

Discussion