JavaScriptのView Transition APIの使い方【DOM更新をなめらかに切り替える】
絞り込みやテーマ切り替えでDOMを更新すると、内容が瞬時に入れ替わって変化を追いにくいことがあります。View Transition APIを使うと、更新前と更新後の表示をブラウザに捉えさせ、その間をCSSでアニメーションできます。
この記事では、記事カードをCSS/JavaScriptで絞り込むUIにdocument.startViewTransition()を追加します。未対応環境ではアニメーションなしで更新するフォールバックも入れます。
View Transition APIの流れ
- 現在の表示状態をブラウザが捉える
startViewTransition()へ渡した更新処理を実行する- 更新後の表示状態をブラウザが捉える
- 古い表示と新しい表示の間を疑似要素でアニメーションする
完成コード
HTML
<main class="app-shell">
<img class="nines-logo" src="https://kjnine.com/wp-content/themes/kjnines_theme/images/svg2/nines_logo.svg" alt="NINES">
<header>
<p class="eyebrow">VIEW TRANSITION</p>
<h1>記事カードの表示切り替え</h1>
<div class="filter-buttons" aria-label="記事の絞り込み">
<button type="button" data-filter="all" aria-pressed="true">すべて</button>
<button type="button" data-filter="css" aria-pressed="false">CSS</button>
<button type="button" data-filter="js" aria-pressed="false">JavaScript</button>
</div>
</header>
<section id="articleGrid" class="article-grid" aria-live="polite"></section>
</main>CSS
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
padding: 32px;
background: #f1f4f8;
color: #111;
font-family: system-ui, sans-serif;
}
.app-shell {
position: relative;
width: min(100%, 920px);
margin: 0 auto;
}
.nines-logo {
position: absolute;
top: 0;
right: 0;
width: 64px;
opacity: 0.14;
}
.eyebrow {
color: #0057ff;
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0.14em;
}
.filter-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 24px 0;
}
button {
min-height: 42px;
padding: 8px 14px;
border: 1px solid #111;
background: #fff;
color: #111;
cursor: pointer;
font: inherit;
font-weight: 700;
}
button[aria-pressed="true"] {
border-color: #0057ff;
background: #0057ff;
color: #fff;
}
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
view-transition-name: article-grid;
}
.article-card {
min-height: 180px;
padding: 22px;
border: 1px solid #d7deea;
background: #fff;
}
.article-card span {
color: #0057ff;
font-size: 0.78rem;
font-weight: 800;
}
::view-transition-old(article-grid) {
animation: 180ms ease both fade-out;
}
::view-transition-new(article-grid) {
animation: 260ms ease both fade-in;
}
@keyframes fade-out {
to {
opacity: 0;
transform: translateY(-8px);
}
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*) {
animation-duration: 0.01ms;
}
}JavaScript
const articles = [
{ category: 'css', title: 'CSS Anchor Positioning入門' },
{ category: 'js', title: 'View Transition API入門' },
{ category: 'css', title: 'Scroll-driven Animations入門' },
{ category: 'js', title: 'Popover APIを制御する' },
];
const grid = document.querySelector('#articleGrid');
const buttons = document.querySelectorAll('[data-filter]');
function render(filter = 'all') {
const filtered = filter === 'all'
? articles
: articles.filter((article) => article.category === filter);
grid.innerHTML = filtered.map((article) => `
<article class="article-card">
<span>${article.category.toUpperCase()}</span>
<h2>${article.title}</h2>
<p>標準Web APIでUIを作る実装メモです。</p>
</article>
`).join('');
}
function updateFilter(button) {
buttons.forEach((item) => {
item.setAttribute('aria-pressed', String(item === button));
});
render(button.dataset.filter);
}
buttons.forEach((button) => {
button.addEventListener('click', () => {
if (!document.startViewTransition) {
updateFilter(button);
return;
}
document.startViewTransition(() => updateFilter(button));
});
});
render();最小のJavaScript
document.startViewTransition(() => {
updateTheDOM();
});コールバック内でDOMを更新します。APIが更新前後の状態を扱うため、アニメーション用にDOMを二重管理する必要がありません。サンプルでは絞り込み関数をそのままコールバックへ渡しています。
未対応環境のフォールバック
View Transition APIがなくても、絞り込み機能そのものは動くべきです。if (!document.startViewTransition)で確認し、未対応なら更新関数を直接呼びます。アニメーションを必須機能にしないことが重要です。
view-transition-nameを付ける
特定の要素を個別に扱う場合は、CSSでview-transition-nameを指定します。サンプルでは記事グリッド全体へarticle-gridという名前を付け、古いグリッドと新しいグリッドを別々にアニメーションしています。
疑似要素で動きを調整する
| 疑似要素 | 役割 |
|---|---|
::view-transition-old(name) | 更新前のスナップショット |
::view-transition-new(name) | 更新後のスナップショット |
::view-transition-group(name) | 古い表示と新しい表示をまとめるグループ |
更新前を上へ消し、更新後を下から表示するようにすると、フィルターが切り替わったことを短い動きで伝えられます。大きく移動させず、200〜300ms程度の軽い変化から調整するのがおすすめです。
使いやすい場面
- 記事一覧や商品一覧の絞り込み
- タブの内容切り替え
- ライト/ダークテーマの切り替え
- 詳細パネルの展開
- SPA内の画面遷移
使わない方がよい場面
- 更新自体が重く、アニメーション以前に待ち時間が長い
- 動きが業務操作の速度を下げる
- 変化が小さく、アニメーションする意味がない
- 視覚効果が内容理解を邪魔する
動きを減らす設定へ対応する
prefers-reduced-motion: reduceではアニメーション時間をほぼゼロにします。UIの状態変更は残し、動きだけを減らします。派手さより、何が変わったか分かることを優先しましょう。
CodePen埋め込み用エリア
3つのパネルへコードを貼り、CSSとJavaScriptボタンを交互に押してください。次にview-transition-nameを外し、標準のルート遷移との違いを比べると理解しやすくなります。
あわせて読みたい
- JavaScriptで検索条件をURLに保存する方法
- CSSで作る効果的なちょっとしたアニメーション10選
- JavaScriptで作るよく見るUI 10選
- CSS Scroll-driven Animationsの使い方
参考資料
まとめ
View Transition APIを使うと、DOM更新前後の変化を少ないコードでアニメーションできます。機能本体を先に作り、startViewTransition()は段階的な強化として追加します。フォールバックとprefers-reduced-motionを用意し、短く意味のある動きにしましょう。