第1回

<ViewTransition>でアニメーション遷移を実際に試す

React 19.3で安定版になったViewTransitionコンポーネントを実際に試す。リストの並び替えや要素の出入りにアニメーションを付ける方法を、Vite環境で動かしながら解説する。

·20分で読める

制作・運営:React Guide 運営者

教材の範囲:以下は別プロジェクトで試す手順とコード例です。記事内の見本は静的な説明用表示です。導入先のフレームワーク・外部サービスまでの実行確認は含めていません。

たける
たける リストを並び替えたりタブを切り替えたりするとき、パッと一瞬で変わるんじゃなくてヌルッとアニメーションさせたいんです。今までその度に別のアニメライブラリを入れてたんですけど、もっと手軽な方法ないですか?
りこ
りこ React 19.3で `ViewTransition` が安定版になった。ブラウザの View Transition API を、Reactの状態更新に合わせて扱いやすくしたもの。まさにその用途。

このシリーズ「React最新ラボ」では、最近のReact界隈の新しい技術を手元で動作を確かめるための手順を扱う。初回は、アニメーション系で話題の <ViewTransition> を試す。

このシリーズの前提:useState / useEffect やコンポーネント分割といった基礎は分かっている中級者向け。React基礎が不安なら先に「React基礎 完全ガイド」を。

<ViewTransition> とは何か

ブラウザには View Transition API(document.startViewTransition)という仕組みがある。DOMが切り替わる「前」と「後」のスナップショットを撮り、その差分を自動でアニメーションしてくれるものだ。

ただし素のAPIはDOMを直接操作する前提で、Reactの「状態を変えたら画面が変わる」という宣言的な書き方と噛み合わせるのが面倒だった。<ViewTransition> は、Reactの状態更新に合わせて自動でView Transitionを発火してくれるラッパーになっている。

なつみ
なつみ いいのは、アニメ本体を**CSSの `::view-transition-*` で書ける**こと。JavaScriptでキーフレームを組み立てなくても、見た目の調整はCSS側に寄せられる。

試す準備

2026年9月9日のReact 19.3で安定版になった。この教材を試す別プロジェクトではreactとreact-domを19.3系にそろえる。当サイト自身の実行環境は19.2系で、このコードは記事内で実行していない。

npm install react@19.3 react-dom@19.3
npm install -D @types/react@latest @types/react-dom@latest

React 19.3の公式リリースを確認したうえで、npm ls react react-dom で実際に入った版を記録する。アニメーションの表示はブラウザの対応にも依存する。

最小例:要素の出入りにアニメを付ける

まずは一番シンプルな「表示・非表示の切り替え」から。

import { ViewTransition, startTransition, useState } from 'react'

export function HelloToggle() {
  const [show, setShow] = useState(false)

  return (
    <>
      <button
        onClick={() => {
          // ★ 状態更新を startTransition で包むのがポイント
          startTransition(() => setShow((v) => !v))
        }}
      >
        切り替え
      </button>

      {show && (
        <ViewTransition>
          <p className="card">こんにちは!</p>
        </ViewTransition>
      )}
    </>
  )
}
たける
たける なんで `setShow` をそのまま呼ばずに `startTransition` で包むんですか?
りこ
りこ View Transition は「トランジションとして実行された更新」だけをアニメ対象にする決まりだから。ただの `setState` では発火しない。`startTransition` / `useTransition` / `` / `useDeferredValue` のどれか経由で起きた更新が対象になる。

ボタンを押すと、<p> が単に出現するのではなく、ブラウザ標準のクロスフェードで現れる。

表示イメージ(操作・計測結果ではありません):押す前
表示イメージ(操作・計測結果ではありません):押した後(フワッとフェードインしながら出現)

こんにちは!

アニメを自分で決める:enter / exit とCSS

ブラウザ標準のクロスフェードではなく、自分でアニメを決めたいときは、enter(追加時)・exit(削除時)にクラス名を渡し、CSSの ::view-transition-new / ::view-transition-old に書く。

<ViewTransition enter="slide-in" exit="slide-out">
  <p className="card">こんにちは!</p>
</ViewTransition>
@keyframes slideIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* enter="slide-in" → 新しく入ってくる側のスナップショット */
::view-transition-new(.slide-in) {
  animation: slideIn 300ms ease;
}
/* exit="slide-out" → 消えていく側のスナップショット */
::view-transition-old(.slide-out) {
  animation: slideIn 300ms ease reverse;
}

enter / exit などのプロパティに渡せる値は3種類。

値 意味
"auto" ブラウザ標準のクロスフェード
"none" アニメーションしない
"クラス名" ::view-transition-*(.クラス名) のCSSを当てる

主なプロパティ

<ViewTransition> は「どのタイミングでアニメするか」をプロパティで指定する。

prop 動くタイミング
enter この要素が新しく追加されたとき
exit この要素が削除されたとき
update 中身が変わった・レイアウトがずれたとき
share 同じ name を持つ別の <ViewTransition> が出入りしたとき(共有要素遷移)
default 上記が未指定のときのフォールバック

リストの並び替えを滑らかにする(update)

同じインスタンスが並び替えで移動する場合はupdateのアニメーション。Reactの同一性は安定したkeyで保つ。shareは同じnameを持つ別の境界が入れ替わる場合であり、この並び替えとは区別する。

import { ViewTransition, startTransition, useState } from 'react'

const initial = [
  { id: 'a', label: 'りんご' },
  { id: 'b', label: 'みかん' },
  { id: 'c', label: 'ぶどう' },
]

export function SortableList() {
  const [items, setItems] = useState(initial)

  const shuffle = () => {
    startTransition(() => {
      setItems((prev) => [...prev].reverse())
    })
  }

  return (
    <>
      <button onClick={shuffle}>並び替え</button>
      <ul>
        {items.map((item) => (
          // ★ name は同時に存在する中で一意にする
          <ViewTransition key={item.id} name={`fruit-${item.id}`}>
            <li>{item.label}</li>
          </ViewTransition>
        ))}
      </ul>
    </>
  )
}
たける
たける `name` に `fruit-${item.id}` ってわざわざIDを混ぜてるのは?
りこ
りこ 同じ `name` を持つ `` が同時に2つマウントされるとエラーになる。だから要素ごとに一意になるよう名前空間を付ける。固定文字列だと衝突する。

遷移の「向き」でアニメを変える:addTransitionType

「次へ」は左にスライド、「戻る」は右にスライド――のように、同じコンポーネントでも文脈でアニメを変えたいときは addTransitionType() を使う。

import { addTransitionType, startTransition } from 'react'

function goNext() {
  startTransition(() => {
    addTransitionType('nav-forward')
    setPage((p) => p + 1)
  })
}

function goBack() {
  startTransition(() => {
    addTransitionType('nav-back')
    setPage((p) => p - 1)
  })
}

プロパティ側は、型ごとに値を分けたオブジェクトで受ける。

<ViewTransition
  enter={{
    'nav-forward': 'slide-left',
    'nav-back': 'slide-right',
    default: 'none',
  }}
>
  {/* ページ本体 */}
</ViewTransition>

現場で使うときの注意

なつみ
なつみ 動きが派手な分、`prefers-reduced-motion` の人への配慮は必須。アニメを切る一文を入れておく。
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
  • 対象バージョン:React 19.3。19.2の安定版にそのままコピーしても同じAPIは使えない。
  • 追加・削除される境界の位置:ViewTransition自身が追加・削除されるのか、その外側のDOMごと入れ替わるのかで挙動が変わる。単に前に兄弟ノードがあると禁止、という意味ではない。
  • 対応ブラウザ:View Transition API は Chromium 系が先行。未対応ブラウザではアニメせず一瞬で切り替わるだけで、壊れはしない(段階的強化)。
📁 第1回の完成コード(並び替えデモ)

フォルダ構成

src/
├── components/
│   └── SortableList.tsx   ← 今回作成
├── index.css              ← ::view-transition-* のCSSを追記
└── App.tsx

SortableList.tsx

import { ViewTransition, startTransition, useState } from 'react'

type Fruit = { id: string; label: string }

const initial: Fruit[] = [
  { id: 'a', label: 'りんご' },
  { id: 'b', label: 'みかん' },
  { id: 'c', label: 'ぶどう' },
]

export const SortableList = () => {
  const [items, setItems] = useState<Fruit[]>(initial)

  const shuffle = () => {
    startTransition(() => {
      setItems((prev) => [...prev].reverse())
    })
  }

  return (
    <div>
      <button onClick={shuffle}>並び替え</button>
      <ul>
        {items.map((item) => (
          <ViewTransition key={item.id} name={`fruit-${item.id}`}>
            <li>{item.label}</li>
          </ViewTransition>
        ))}
      </ul>
    </div>
  )
}

index.css(抜粋)

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

まとめ

  • <ViewTransition> は、ブラウザの View Transition API を Reactの状態更新に繋ぐコンポーネント(19.3で安定化)
  • アニメの発火には startTransition(または useTransition / <Suspense> / useDeferredValue)が必要。ただの setState では動かない
  • 見た目は ::view-transition-old / ::view-transition-new のCSSで、並び替えはupdate、別の境界の入れ替えはnameを使うshareで
  • 使うReactとブラウザの対応を確認し、prefers-reduced-motion も試す

次回は、最小のReactフレームワーク Waku を取り上げる。Server Components を一番小さく試して、このサイトと同じ Cloudflare にデプロイするところまでをやる。

確認してみる

reactとreact-domの版を記録し、反転後の順序を確認する。reduced-motion設定と、アニメしない環境でもボタンが機能するかを比べる。

仕様・手順の参照先

#react#react19#view-transition#animation#intermediate