注意点・ブラウザ対応
ハマりやすいポイントと、本番投入する前に頭に入れておきたい制約まとめ。
名前まわりの落とし穴
同じ view-transition-name が複数あると transition がスキップされる
最頻出のバグ。.card { view-transition-name: card; } のように静的に名前を付けて
その class が複数あると、その瞬間に transition 全体がキャンセルされる(疑似要素が作れないため)。
- リストには動的に id ベースの名前を付ける(
style="view-transition-name: item-3")。 - 対応ブラウザでは
view-transition-name: autoが使える。 - 不要になったタイミングで
noneに戻す。
遷移後にも同じ name が残っていると衝突する
一覧 → 詳細の hero 遷移で、詳細ビューを表示した時点でも一覧ビューがまだ DOM に居ると、
同じ name が両側に存在することになる。
遷移時にどちらか1つだけ DOM に居る状態を保証する。
name の値はグローバル名前空間
スコープという概念がない。サイト全体で1つだけ存在するというつもりで命名する。
コンポーネントごとに card のような汎用語を当てると、別ページや別モジュールで衝突する。
更新コールバックの落とし穴
updateCallback の中で React の setState が反映されない
React は更新を非同期にバッチするので、ナイーブに setState しても
スナップショット時点で DOM が変わっておらず、transition が空振る。
flushSync でその場コミットを強制する。
document.startViewTransition(() => flushSync(() => setRoute("detail")))
非同期 updateCallback は画面を凍らせる
startViewTransition(async () => { await fetch(...) }) のように長い await を入れると、
その間スナップショットが画面に貼り付いた状態でUIが固まる。
fetch は外で済ませて、updateCallback は同期更新だけにする。
updateCallback が reject すると transition はスキップされる
例外を投げたり Promise を reject すると、ブラウザは「DOM 更新が失敗した」とみなして 即座に古いスナップショットを破棄する。エラー時の見た目を制御したい場合は自分で catch して フォールバックの状態に切り替える。
同時実行と連打
新しい startViewTransition が始まると、進行中の transition は即スキップされる。
ボタン連打などで複数発火するとカクつくので、進行中フラグで弾く / debounce する。
let busy = null
async function navigate(url) {
if (busy) return // 進行中は無視(または busy.skipTransition())
const apply = () => doNavigate(url)
if (!document.startViewTransition) return apply()
busy = document.startViewTransition(apply)
await busy.finished.catch(() => {})
busy = null
}
レイアウト系の罠
position: fixed 要素は要注意
スクロールに追従する fixed ヘッダなどは、スナップショットされる時の位置と新しい位置が
ズレると、transition 中に飛んだように見えることがある。
独立した view-transition-name を当てて形を保ったままモーフさせるか、
old/new の animation を none にして即時切替させる。
contain / overflow: hidden の影響
スナップショット時のサイズは要素のレイアウトサイズに依存する。overflow: hidden で
切れている部分はスナップショットには含まれない。動かす時に切れ目が見えるなら、
モーフ対象の要素は overflow: visible 側に寄せる。
背景色の継承
::view-transition-old/new は背景透明として扱われる。下に背景がない HTML だと、
遷移中に下のレイヤーが透けて見えてチラつく。html, body に確実に背景色を当てておく。
パフォーマンス
- スナップショットはラスタライズなので大きな要素・大量の name は重い。
- 名前付きで動かす要素は本当に必要なものだけ。背景全体に大きな name を当てると遅くなる。
videoやcanvasは動的に変化しているので、スナップショット時の1フレームが固定画像として写る。- 長い
animation-durationは気持ち良く見えても、UIブロック時間を伸ばす。300〜400ms がおおよその上限感。
アクセシビリティ
prefers-reduced-motion を尊重する
派手な演出はかならず減衰版を用意する。デフォルトのフェードはまだ穏やかだが、カスタムの スライドや拡大は明確に「動き」として認知される。
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
フォーカス・スクリーンリーダ
transition 中はクリック・キー入力が効かない。フォーカスは更新後の DOM に正しく移っているかを 確認する。ライブリージョンの読み上げは transition の有無で変わらない。
セキュリティ・プライバシー
- クロスオリジン遷移では発火しない。情報漏洩の防止のため。
- iframe 内の同一ドキュメント transition は基本動くが、ホストとの相互作用は限定的。
- ナビゲーション API(
navigation.activation等)は同一オリジンのエントリ情報のみ参照可能。
ブラウザ対応の現状(2026 春時点)
| 機能 | Chromium | Safari | Firefox |
|---|---|---|---|
| 同一ドキュメント(Level 1) | 111+(2023〜)安定 | 18.0+(2024〜)安定 | 順次対応中。dom.viewTransitions.enabled フラグ経由が長く、最新版で安定化が進む |
クロスドキュメント(@view-transition) |
126+(2024〜)安定 | 順次対応(バージョンによる) | 未対応 / 進行中の場合あり |
types / :active-view-transition-type() |
対応 | 新しめ | 未対応 / 進行中の場合あり |
view-transition-class |
新しめ | 未対応 / 進行中の場合あり | 未対応 / 進行中の場合あり |
正確な最新状況はcaniuseと MDN を都度確認するのが安全。
document.startViewTransition の有無で必ず分岐する。
未対応ブラウザでも普通に動くのが大前提で、View Transition は「対応ブラウザで気持ち良く動く強化レイヤー」
として設計する。
判断フロー
- 「DOM が変わる瞬間」が明確な操作か? → Yes ならまず
startViewTransitionを試す。 - 連続で更新があるなら、その境目だけを transition で囲む。
- 個別の要素を「形ごと動かしたい」場合だけ
view-transition-nameを付ける。乱発しない。 - 動きのカスタマイズが必要なら、old / new の
animationを上書きする(CSS 側で完結させる)。 - SPA で使うときは framework のコミットタイミング(
flushSync/nextTick)に必ず合わせる。