注意点・ブラウザ対応

ハマりやすいポイントと、本番投入する前に頭に入れておきたい制約まとめ。

名前まわりの落とし穴

同じ view-transition-name が複数あると transition がスキップされる

最頻出のバグ。.card { view-transition-name: card; } のように静的に名前を付けて その class が複数あると、その瞬間に transition 全体がキャンセルされる(疑似要素が作れないため)。

遷移後にも同じ 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 に確実に背景色を当てておく。

パフォーマンス

アクセシビリティ

prefers-reduced-motion を尊重する

派手な演出はかならず減衰版を用意する。デフォルトのフェードはまだ穏やかだが、カスタムの スライドや拡大は明確に「動き」として認知される。

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

フォーカス・スクリーンリーダ

transition 中はクリック・キー入力が効かない。フォーカスは更新後の DOM に正しく移っているかを 確認する。ライブリージョンの読み上げは transition の有無で変わらない。

セキュリティ・プライバシー

ブラウザ対応の現状(2026 春時点)

機能ChromiumSafariFirefox
同一ドキュメント(Level 1) 111+(2023〜)安定 18.0+(2024〜)安定 順次対応中。dom.viewTransitions.enabled フラグ経由が長く、最新版で安定化が進む
クロスドキュメント(@view-transition 126+(2024〜)安定 順次対応(バージョンによる) 未対応 / 進行中の場合あり
types / :active-view-transition-type() 対応 新しめ 未対応 / 進行中の場合あり
view-transition-class 新しめ 未対応 / 進行中の場合あり 未対応 / 進行中の場合あり

正確な最新状況はcaniuseMDN を都度確認するのが安全。

フォールバックは必須

document.startViewTransition の有無で必ず分岐する。
未対応ブラウザでも普通に動くのが大前提で、View Transition は「対応ブラウザで気持ち良く動く強化レイヤー」 として設計する。

判断フロー

  1. 「DOM が変わる瞬間」が明確な操作か? → Yes ならまず startViewTransition を試す。
  2. 連続で更新があるなら、その境目だけを transition で囲む。
  3. 個別の要素を「形ごと動かしたい」場合だけ view-transition-name を付ける。乱発しない。
  4. 動きのカスタマイズが必要なら、old / new の animation を上書きする(CSS 側で完結させる)。
  5. SPA で使うときは framework のコミットタイミング(flushSync / nextTick)に必ず合わせる。