テクノロジー
ノウハウ・TIPS

Next.js v15.2で使えるようになった View Transition API を試してみた

Next.js v15.2 から、試験的な機能としてViewTransitionコンポーネントが追加され、ViewTransions APIを使用してページ遷移やDOMの更新にアニメーションをつけることができるようになりました。

これまでNext.jsで実装するには何かと面倒だったViewTransiions APIですが、ViewTransiionコンポーネントを使用することでシンプルに実装できるようになっています。

この記事ではサンプルコードを使って、ページ間の遷移でのViewTransitionの使い方やコンポーネント単位のアニメーションの例を紹介したいと思います。


前提条件なのですが、Next.jsは v15.2 以降のバージョンを使用するようにしてください。
また、ViewTransitionを有効にするにはnext.config.tsのexperimental.viewTransition を有効にする必要があるため忘れないように注意してください。

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  experimental: {
    viewTransition: true,
  },
};

export default nextConfig;

ちなみに、ViewTransitionコンポーネントは現時点では試験的な機能となっているため、 unstable_ViewTransition と定義されています。
下記のようにViewTransiionにリネームして使用するのが良いかと思います。

import { unstable_ViewTransition as ViewTransition } from "react"

また、サンプルを実行して確認する際には、ViewTransitions APIに対応したブラウザを使用するようにしてください。

対応してるブラウザ

各サンプルのファイルですが、src/appディレクトリに新しくディレクトリを作成して、その中にファイルを作成して実行するようにしてください(global.cssは、src/app/global.cssに追記)。


まずはページ遷移の際に数値がアニメーションするシンプルなカウンターアプリの例です。
layout.tsxの中で、childrenをViewTransitionコンポーネントで囲むことで、ページ遷移のたびにアニメーションが発生するようになっています。
ViewTransitionコンポーネントのnameプロパティに count を指定することで、view-transition-name: countが暗黙的に付与されるため、それを利用してcssでanimationの設定を行っています。

app/count/layout.tsx

import { unstable_ViewTransition as ViewTransition } from "react"
import Link from "next/link";
import { use, ReactNode } from "react";

export default function CountLayout({ children, params }: { params: Promise<{ value: string }>, children: ReactNode }) {
  const { value } = use(params);

  return (
    <div>
      <Link href={`/count/${parseInt(value, 10) + 1}`}>Count Up</Link>
      <ViewTransition name="count">
        {children}
      </ViewTransition>
    </div>
  );
}

app/count/page.tsx

import { use } from "react"
import styles from './page.module.css'

export default function CountPage({ params }: { params: Promise<{ value: string }> }) {
  const { value } = use(params)

  return (
    <div>
      <div className={styles.count}>
        <p>{ value }</p>
      </div>
    </div>
  )
}

app/count/page.module.css

.count {
  display: grid;
  font-size: 100px;
  place-items: center;
  width: 100vw;
}

app/global.css

::view-transition-old(count) {
  animation: fade-slide-to 0.3s ease-in-out;
}
::view-transition-new(count) {
  animation: fade-slide-from 0.3s ease-in-out;
}

@keyframes fade-slide-from {
  from {
    opacity: 0;
    transform: translateY(-100px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fade-slide-to {
  from {
    opacity: 1;
    transform: translateY(0);
  }

  to {
    opacity: 0;
    transform: translateY(100px);
  }
}



次は、divのstyleを変更したときにアニメーションでdivを移動させるサンプルを作成してみました
stateを更新することで、left, right, top, bottomの値を設定し、position: absoluteなboxを画面上で動かすようなアニメーションを実現しています。
startTransitionを使用してstateの更新を遅らせることがポイントです。
ViewTransition で囲んだ要素に対して、startTransition(() => setState(...))で状態を更新することで、View Transitionが発動しています。

app/box/page.tsx

'use client'

import styles from './page.module.css';
import { useState, unstable_ViewTransition as ViewTransition, startTransition } from "react"

export default function BoxPage() {
  const [vertical, setVertical] = useState('top');
  const [horizontal, setHorizontal] = useState('left');

  return (
    <div>
      <div className={styles.buttons}>
        <button onClick={() => startTransition(() => setVertical('top'))}>top</button>
        <button onClick={() => startTransition(() => setVertical('bottom'))}>bottom</button>
        <button onClick={() => startTransition(() => setHorizontal('left'))}>left</button>
        <button onClick={() => startTransition(() => setHorizontal('right'))}>right</button>
      </div>
      <div className={styles.wrapper}>
        <ViewTransition>
          <div
            className={styles.box}
            style={{
              [vertical]: 0,
              [horizontal]: 0,
            }}
          ></div>
        </ViewTransition>
      </div>
    </div>
  );
}

app/box/page.module.css

.wrapper {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 90vh;
  position: relative;
}

.box {
  width: 200px;
  height: 200px;
  background-color: red;
  position: absolute;
  z-index: 0;
}

.buttons {
  background-color: white;
  display: flex;
  gap: 10px;
  padding: 10px;
  z-index: 1;
}

.button {
  padding: 10px;
  border: 1px solid black;
  border-radius: 5px;
  color: black;
  cursor: pointer;
}



最後に、ユーザー一覧と詳細ページを切り替えるときに、画像や名前をスムーズにアニメーションさせるサンプルです。
一覧の中からユーザーをクリックすると、そのユーザーの画像や名前がふわっと大きくなって詳細ビューに表示されます。
ViewTransition name="image-1" のように、画像と名前に一意の名前を付けてトランジションを関連付けることで、Linkを使ってページ遷移をしたときにリストの画像と名前が画面中央の画像と名前にスムーズにつながるようなアニメーションを実現することができます。

app/user/[[...id]]/page.tsx

import { use, unstable_ViewTransition as ViewTransition } from "react"
import styles from './page.module.css';
import Link from "next/link";

const users = [
  {
    id: '1',
    name: "Alice Johnson",
    image: "https://randomuser.me/api/portraits/women/1.jpg",
  },
  {
    id: '2',
    name: "Bob Smith",
    image: "https://randomuser.me/api/portraits/men/2.jpg",
  },
  {
    id: '3',
    name: "Charlie Davis",
    image: "https://randomuser.me/api/portraits/men/3.jpg",
  },
  {
    id: '4',
    name: "Diana Lee",
    image: "https://randomuser.me/api/portraits/women/4.jpg",
  },
  {
    id: '5',
    name: "Ethan Brown",
    image: "https://randomuser.me/api/portraits/men/5.jpg",
  },
];

export default function UserPage ({ params }: { params: Promise<{ id?: string[] }>}) {
  const id = use(params).id?.[0]
  const user = users.find((user) => user.id === id)
  const list = users.filter(user => user.id !== id)

  return (
    <div className={styles.layout}>
      <nav className={styles.nav}>
        <ul>
          {list.map((user) => (
            <li key={user.id}>
              <Link href={`/user/${user.id}`}>
                <ViewTransition name={`image-${user.id}`}>
                  <img src={user.image} alt={user.name} width={50} height={50} />
                </ViewTransition>
                <ViewTransition name={`name-${user.id}`}>
                  <h1>{user.name}</h1>
                </ViewTransition>
              </Link>
            </li>
          ))}
        </ul>
      </nav>
      <div className={styles.main}>
        {user && (
          <div>
            <ViewTransition name={`name-${user.id}`}>
              <h1>{user.name}</h1>
            </ViewTransition>
            <ViewTransition name={`image-${user.id}`}>
              <img src={user.image} alt={user.name} height={100} width={100} />
            </ViewTransition>
            <p>ID: {user.id}</p>
            <p>Name: {user.name}</p>
            <p>Image: {user.image}</p>
          </div>
        )}
      </div>
    </div>
  )
}

app/user/[[...id]]/page.module.css

.layout {
  align-items: flex-start;
  display: flex;
  flex-direction: row;
  gap: 20px;
  padding: 30px;
}

.main {
  align-items: flex-start;
  flex: 1;
  display: flex;
  justify-content: center;
}

.nav {
  ul {
    display: flex;
    flex-direction: column;
    gap: 20px;
    list-style: none;
  }
}



いかがでしたでしょうか。
ViewTransitionコンポーネントが追加されたことで、Reactでも簡単にViewTransitions APIを使用することができるようになりました。
現時点では試験的な機能なのでプロダクション環境で使用するにはまだ早いといった感じではありますが、触っていて面白い機能なので、ぜひぜひ試してみてくださいね。

前の記事へ

一覧へ戻る

「テクノロジー」カテゴリの最新記事

PAGE TOP