2 つの異なるドキュメント間でビューの遷移が発生する場合、これはドキュメント間のビューの遷移と呼ばれます。これは通常、マルチページ アプリケーション(MPA)に当てはまります。ドキュメント間のビュー遷移は、Chrome 126 以降の Chrome でサポートされています。
対応ブラウザ
ドキュメント間のビュー遷移では、同一ドキュメントでのビュー遷移とまったく同じ構成要素と原則が使用されますが、これは非常に意図的なものです。
- ブラウザは、古いページと新しいページの両方で一意の
view-transition-name
を持つ要素のスナップショットを取得します。 - レンダリングが抑制されている間に DOM が更新されます。
- 最後に、遷移は CSS アニメーションを利用しています。
同一ドキュメント ビュー遷移と比較した場合の違いは、ドキュメント間でビュー遷移を行う場合、ビュー遷移を開始するために document.startViewTransition
を呼び出す必要がない点です。代わりに、ドキュメント間のビュー遷移のトリガーは、ページ間の同一オリジンのナビゲーションです。これは、通常、ウェブサイトのユーザーがリンクをクリックすることで実行されるアクションです。
つまり、2 つのドキュメント間のビュー移行を開始するために呼び出す API はありません。ただし、次の 2 つの条件を満たす必要があります。
- 両方のドキュメントが同じオリジンに存在している必要があります。
- ビューの遷移を許可するには、両方のページでオプトインする必要があります。
これらの条件については、このドキュメントの後半で説明します。
ドキュメント間のビュー遷移は同一オリジンのナビゲーションに限定される
ドキュメント間のビュー遷移は、同一オリジンのナビゲーションに限定されます。参加している両方のページのオリジンが同じである場合、そのナビゲーションは同一オリジンと見なされます。
ページのオリジンは、使用されるスキーム、ホスト名、ポートの組み合わせです(web.dev で詳しく説明)。
たとえば、developer.chrome.com
から developer.chrome.com/blog
に移動するときに、同じオリジンであるため、ドキュメント間のビュー遷移を設定できます。developer.chrome.com
から www.chrome.com
に移動する場合、これはクロスオリジンかつ同一サイトであるため、この遷移は行えません。
ドキュメント間のビューの切り替えはオプトイン
2 つのドキュメント間でドキュメント間のビュー遷移を行うには、参加する両方のページでこの機能を有効にする必要があります。これは、CSS の @view-transition
アットルールで行います。
@view-transition
アットルールで、navigation
記述子を auto
に設定して、ドキュメント間、同一オリジン ナビゲーションでビュー遷移を有効にします。
@view-transition {
navigation: auto;
}
navigation
記述子を auto
に設定すると、次の NavigationType でビュー遷移を許可できます。
traverse
push
またはreplace
: ユーザーがブラウザの UI メカニズムを使用して有効化を開始した場合。
auto
から除外されるナビゲーションは、URL アドレスバーを使用したナビゲーションやブックマークのクリックなどです。また、ユーザーまたはスクリプトによって開始されたリロードも含まれます。
ナビゲーションに時間がかかりすぎる(Chrome の場合は 4 秒を超える)場合、TimeoutError
DOMException
でビュー遷移がスキップされます。
ドキュメント間のビュー遷移のデモ
ビュー遷移を使用してスタック ナビゲータのデモを作成する次のデモをご覧ください。ここでは document.startViewTransition()
が呼び出されておらず、ページ間の移動によってビューの遷移がトリガーされます。
ドキュメント間のビュー遷移をカスタマイズする
ドキュメント間のビュー遷移をカスタマイズする場合、使用できるウェブ プラットフォームの機能があります。
これらの機能は View Transition API の仕様そのものではありませんが、一緒に使用するように設計されています。
pageswap
イベントと pagereveal
イベント
ドキュメント間のビュー遷移をカスタマイズできるように、HTML 仕様には、使用できる 2 つの新しいイベント(pageswap
と pagereveal
)が含まれています。
この 2 つのイベントは、ビュー遷移が発生するかどうかにかかわらず、同一オリジンのクロスドキュメント ナビゲーションのたびに呼び出されます。2 つのページ間でビューが遷移しようとしている場合は、これらのイベントの viewTransition
プロパティを使用して、ViewTransition
オブジェクトにアクセスできます。
pageswap
イベントは、ページの最後のフレームがレンダリングされる前に発生します。これにより、古いスナップショットが取得される直前に、削除されるページに最後の変更を加えることができます。pagereveal
イベントは、ページが初期化または再アクティベートされた後、最初のレンダリング オポチュニティの前に発生します。これにより、新しいスナップショットを取得する前に新しいページをカスタマイズできます。
たとえば、これらのイベントを使用して、一部の view-transition-name
値をすばやく設定または変更したり、sessionStorage
に対してデータを読み書きすることでドキュメント間でデータを渡し、実際に実行する前にビュー遷移をカスタマイズしたりできます。
let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() === 'a') return;
lastClickX = event.clientX;
lastClickY = event.clientY;
});
// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
if (event.viewTransition && lastClick) {
sessionStorage.setItem('lastClickX', lastClickX);
sessionStorage.setItem('lastClickY', lastClickY);
}
});
// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
if (event.viewTransition) {
lastClickX = sessionStorage.getItem('lastClickX');
lastClickY = sessionStorage.getItem('lastClickY');
}
});
必要に応じて、両方のイベントで移行をスキップすることもできます。
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
if (goodReasonToSkipTheViewTransition()) {
e.viewTransition.skipTransition();
}
}
}
pageswap
と pagereveal
の ViewTransition
オブジェクトは 2 つの異なるオブジェクトです。また、さまざまな Promise の処理も異なります。
pageswap
: ドキュメントが非表示になると、古いViewTransition
オブジェクトはスキップされます。その場合、viewTransition.ready
は拒否され、viewTransition.finished
は解決します。pagereveal
: この時点でupdateCallBack
Promise はすでに解決されています。viewTransition.ready
プロミスとviewTransition.finished
プロミスを使用できます。
ナビゲーションの有効化情報
pageswap
イベントと pagereveal
イベントの両方で、古いページと新しいページの URL に基づいてアクションを実行することもできます。
たとえば、MPA スタック ナビゲーターでは、使用するアニメーションのタイプはナビゲーション パスによって異なります。
- 概要ページから詳細ページに移動するときは、新しいコンテンツを右から左にスライドインする必要があります。
- 詳細ページから概要ページに移動するときに、古いコンテンツが左から右にスライドアウトする必要があります。
そのためには、pageswap
の場合はこれから、または pagereveal
の場合は、ちょうど起きたばかりのナビゲーションに関する情報が必要です。
これにより、ブラウザは同一生成元のナビゲーションに関する情報を保持する NavigationActivation
オブジェクトを公開できるようになりました。このオブジェクトは、Navigation API の navigation.entries()
にある、使用されたナビゲーション タイプ、現在のデスティネーション、最終的なデスティネーションの履歴エントリを公開します。
アクティブなページでは、navigation.activation
を介してこのオブジェクトにアクセスできます。pageswap
イベントでは、e.activation
を介してこの値にアクセスできます。
こちらの Profiles デモでは、pageswap
イベントと pagereveal
イベントの NavigationActivation
情報を使用して、ビュー遷移に参加する必要がある要素に view-transition-name
値を設定しています。
これにより、リスト内のすべてのアイテムに事前に view-transition-name
を装飾する必要がなくなります。代わりに、JavaScript を使用して、必要な要素でのみジャストインタイムに処理されます。
コードは次のとおりです。
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove view-transition-names after snapshots have been taken
// (this to deal with BFCache)
await e.viewTransition.finished;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';
// Remove names after snapshots have been taken
// so that we're ready for the next navigation
await e.viewTransition.ready;
document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
}
}
});
また、ビューの遷移が実行された後に view-transition-name
値を削除することで、コード自体をクリーンアップします。これにより、ページは連続したナビゲーションの準備ができ、履歴の移動も処理できるようになります。
これを支援するために、view-transition-name
を一時的に設定するユーティリティ関数を使用します。
const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
for (const [$el, name] of entries) {
$el.style.viewTransitionName = name;
}
await vtPromise;
for (const [$el, name] of entries) {
$el.style.viewTransitionName = '';
}
}
上記のコードは次のように簡素化できます。
// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
if (e.viewTransition) {
const targetUrl = new URL(e.activation.entry.url);
// Navigating to a profile page
if (isProfilePage(targetUrl)) {
const profile = extractProfileNameFromUrl(targetUrl);
// Set view-transition-name values on the clicked row
// Clean up after the page got replaced
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.finished);
}
}
});
// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
if (e.viewTransition) {
const fromURL = new URL(navigation.activation.from.url);
const currentURL = new URL(navigation.activation.entry.url);
// Navigating from a profile page back to the homepage
if (isProfilePage(fromURL) && isHomePage(currentURL)) {
const profile = extractProfileNameFromUrl(currentURL);
// Set view-transition-name values on the elements in the list
// Clean up after the snapshots have been taken
setTemporaryViewTransitionNames([
[document.querySelector(`#${profile} span`), 'name'],
[document.querySelector(`#${profile} img`), 'avatar'],
], e.viewTransition.ready);
}
}
});
レンダリング ブロックを使用してコンテンツが読み込まれるのを待つ
対応ブラウザ
場合によっては、特定の要素が新しい DOM に存在するまで、ページの最初のレンダリングを保留することが必要になることがあります。これにより、フラッシュを回避し、アニメーション化する状態が安定するようにします。
<head>
で、次のメタタグを使用して、ページの最初のレンダリング前に存在する必要がある要素 ID を 1 つ以上定義します。
<link rel="expect" blocking="render" href="#section1">
このメタタグは、コンテンツを読み込むのではなく、要素が DOM に存在する必要があることを意味します。たとえば画像の場合、DOM ツリーに指定された id
を含む <img>
タグが存在するだけで、条件が true と評価されます。画像自体はまだ読み込み中である可能性があります。
レンダリング ブロックを全面的に導入する前に、増分レンダリングはウェブの基本的な要素であるため、レンダリングをブロックする際には注意が必要です。レンダリングをブロックする影響は、ケースバイケースで評価する必要があります。デフォルトでは、Core Web Vitals への影響を測定して、blocking=render
がユーザーに及ぼす影響を積極的に測定、評価できる場合を除き、blocking=render
を使用しないでください。
ドキュメント間のビュー遷移で遷移タイプを表示する
ドキュメント間のビュー遷移では、アニメーションやキャプチャする要素をカスタマイズするためのビュー遷移タイプもサポートされています。
たとえば、ページネーションの次のページまたは前のページに移動する場合、シーケンス内の上位のページに移動するか下位のページに移動するかに応じて、異なるアニメーションを使用するとよいでしょう。
これらのタイプを事前に設定するには、@view-transition
アットルールにタイプを追加します。
@view-transition {
navigation: auto;
types: slide, forwards;
}
タイプをその場で設定するには、pageswap
イベントと pagereveal
イベントを使用して e.viewTransition.types
の値を操作します。
window.addEventListener("pagereveal", async (e) => {
if (e.viewTransition) {
const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
e.viewTransition.types.add(transitionType);
}
});
タイプは、古いページの ViewTransition
オブジェクトから新しいページの ViewTransition
オブジェクトに自動的に引き継がれることはありません。アニメーションを期待どおりに実行するには、少なくとも新しいページで使用するタイプを決定する必要があります。
これらのタイプに対応するには、同じドキュメント内のビュー遷移の場合と同じように、:active-view-transition-type()
疑似クラス セレクタを使用します。
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
&::view-transition-old(root) {
animation-name: fade-out, scale-down;
}
&::view-transition-new(root) {
animation-delay: 0.25s;
animation-name: fade-in, scale-up;
}
}
型はアクティブ ビュー遷移にのみ適用されるため、ビュー遷移が終了すると、タイプは自動的にクリーンアップされます。そのため、型は BFCache などの機能と連携して適切に機能します。
デモ
次のページネーションのデモでは、移動先のページ番号に応じてページのコンテンツが前後にスライドします。
使用する遷移タイプは、pagereveal
イベントと pageswap
イベントで、移動元と移動先の URL を確認することで決定されます。
const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
const currentURL = new URL(fromNavigationEntry.url);
const destinationURL = new URL(toNavigationEntry.url);
const currentPathname = currentURL.pathname;
const destinationPathname = destinationURL.pathname;
if (currentPathname === destinationPathname) {
return "reload";
} else {
const currentPageIndex = extractPageIndexFromPath(currentPathname);
const destinationPageIndex = extractPageIndexFromPath(destinationPathname);
if (currentPageIndex > destinationPageIndex) {
return 'backwards';
}
if (currentPageIndex < destinationPageIndex) {
return 'forwards';
}
return 'unknown';
}
};
フィードバック
デベロッパーの皆様からのフィードバックをお待ちしております。共有するには、GitHub の CSS ワーキング グループに問題を提出して、提案や質問を送信してください。問題の接頭辞に [css-view-transitions]
を付けます。バグが発生した場合は、代わりに Chromium バグを報告してください。