第1回
<ViewTransition>でアニメーション遷移を実際に試す
React 19.3で安定版になったViewTransitionコンポーネントを実際に試す。リストの並び替えや要素の出入りにアニメーションを付ける方法を、Vite環境で動かしながら解説する。
制作・運営:React Guide 運営者
教材の範囲:以下は別プロジェクトで試す手順とコード例です。記事内の見本は静的な説明用表示です。導入先のフレームワーク・外部サービスまでの実行確認は含めていません。
このシリーズ「React最新ラボ」では、最近のReact界隈の新しい技術を手元で動作を確かめるための手順を扱う。初回は、アニメーション系で話題の <ViewTransition> を試す。
このシリーズの前提:
useState/useEffectやコンポーネント分割といった基礎は分かっている中級者向け。React基礎が不安なら先に「React基礎 完全ガイド」を。
<ViewTransition> とは何か
ブラウザには View Transition API(document.startViewTransition)という仕組みがある。DOMが切り替わる「前」と「後」のスナップショットを撮り、その差分を自動でアニメーションしてくれるものだ。
ただし素のAPIはDOMを直接操作する前提で、Reactの「状態を変えたら画面が変わる」という宣言的な書き方と噛み合わせるのが面倒だった。<ViewTransition> は、Reactの状態更新に合わせて自動でView Transitionを発火してくれるラッパーになっている。
試す準備
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@latestReact 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>
)}
</>
)
}ボタンを押すと、<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>
</>
)
}遷移の「向き」でアニメを変える: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>現場で使うときの注意
@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.tsxSortableList.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 19.2の<Activity>とuseEffectEventを試す
React 19.2で安定版になったActivityとuseEffectEventを実際に試す。タブを切り替えても状態が消えないUIと、useEffectの依存配列の悩み解消を動かしながら解説する。
React Bitsでアニメ付きUIを最短実装する
アニメ付きUIコンポーネント集 React Bits を実際に試す。jsrepoでの導入からBlurTextの設置までを、このサイトと同じVite+Tailwind環境で動かしながら解説する。
useState・useRef・useReducerの型定義:TypeScriptで正しく型をつける
TypeScriptでuseState・useRef・useReducerに型をつける方法を解説。型推論が効くケースと明示が必要なケース、useRefのnull初期値問題まで実例付きで説明します。