公開日: 2025 年 8 月 28 日
Google 検索は世界最大級のリーチを誇るため、ユーザー エクスペリエンスの変更は数十億人のユーザーに影響を与える可能性があります。Google は、よりモダンでアプリのようなウェブ エクスペリエンスを長年夢見てきました。AI モードの開発を開始したとき、標準検索から AI モードへの移行がシームレスでつながっていると感じられるようなユーザー エクスペリエンスを構築したいと考えました。ドキュメント間のビュー遷移について聞いたとき、この機能に最適な組み合わせだと確信しました。このケーススタディでは、AI モードのリリースと同時に移行機能を追加した際に得られた知見を紹介します。
クロスドキュメント ビューの切り替えは、ネイティブ ブラウザ ツールに関して画期的な機能であり、今後のウェブのあり方を大きく変えるものと期待しています。
現状を変える
Google 検索には、厳格で保守的なブラウザ サポート要件があります。一般に、限定公開機能の使用は禁止されています。ドキュメント間のビュー遷移については、ピクセル スナップショット API がなく、ビューポート全体を複製するとパフォーマンスに大きな問題が生じるため、ポリフィルは実現不可能であることがわかりました。そのため、この機能をプログレッシブ エンハンスメントとして使用することが、AI モードと同時にリリースする最善の方法でした。ビュー遷移によって作成されたアニメーションはウェブサイトの機能に直接影響しないため、サポートされていないトラフィックでは単に無効になります。これは、遷移アニメーションのない現在の本番環境の状態と同じです。
まず、このプログレッシブ エンハンスメント戦略を社内ユーザーでテストしました。これにより、早期のフィードバックを得ることができ、そのほとんどが肯定的なものでした。寄せられたフィードバックからは、パフォーマンスの問題や、重なり合うスタッキング コンテキストなど、他の機能との意図しない相互作用といったバグも明らかになりました。
この戦略は成功したと考えており、今後も他の新しいブラウザ機能で試していく予定です。
直面した困難とその解決策
レイテンシ、レンダリング ブロック、ウォッチドッグ タイマー
全体として、MPA ビューの切り替えに伴うレイテンシの増加は、ユースケースの 99% で無視できるほどです。特に最新のハードウェアではその傾向が顕著です。ただし、Google 検索ではレイテンシに関する基準が非常に高く、すべてのデバイスで適切に動作するユーザー エクスペリエンスの実現に努めています。当社では、数ミリ秒の遅延も問題になるため、ユーザー エクスペリエンスを損なうことなくクロスドキュメント ビューの切り替えを適切に実装する方法に投資する必要がありました。
レンダリング ブロックは、クロスドキュメント ビュー遷移と相性の良い手法です。受信ドキュメントの疑似要素のスナップショットには、すでにレンダリングされたコンテンツのみを表示できます。そのため、受信ドキュメントのコンテンツをアニメーション化するには、アニメーション化するターゲット要素がレンダリングされるまでブロックをレンダリングする必要があります。これを行うには、HTMLLinkElement
のblocking
属性を使用します。レンダリング ブロックには、受信ドキュメントの DOM ツリーの末尾にある要素を待つことでレイテンシに大きな影響が生じる可能性があるという欠点があります。このトレードオフを適切に調整し、ページのライフサイクルの非常に早い段階でレンダリングされる要素でのみブロックをレンダリングする必要がありました。
<!-- Link tag in the <head> of the incoming document -->
<link blocking="render" href="#target-id" rel="expect">
<!-- Element you want to animate in the <body> of the incoming document -->
<div id="target-id">
some content
</div>
場合によっては、どの要素をブロックするかを正確に指定するだけでは不十分でした。DOM ツリーの先頭付近の要素でレンダリング ブロックが発生した場合でも、特定のデバイスや接続ではレイテンシが追加されることがあります。このようなケースに対応するため、一定時間が経過すると HTMLLinkElement
を削除して、受信したドキュメントのレンダリングを強制的にブロック解除するウォッチドッグ タイマー スクリプトを作成しました。
これを行う簡単な方法は次のとおりです。
function unblockRendering() {
const renderBlockingElements = document.querySelectorAll(
'link[blocking=render]',
);
for (const element of renderBlockingElements) {
element.remove();
}
}
const timeToUnblockRendering = t - performance.now();
if (timeToUnblockRendering > 0) {
setTimeout(unblockRendering, timeToUnblockRendering);
} else {
unblockRendering();
}
保証の制限事項
もう 1 つの問題は、ドキュメント間のビュー遷移の @規則 navigation: auto
がドキュメント内のグローバル レベルで発生することです。クロスドキュメント ビュー遷移の有効範囲を特定のクリック ターゲットのみに限定する組み込みの方法はありません。この変更は大規模なため、Google 検索のナビゲーションの 100% でドキュメント間のビューの切り替えを有効にすることはできませんでした。ユーザーが操作している機能に応じて、ドキュメント間のビュー遷移を動的に有効または無効にする方法が必要でした。この例では、AI モードへの切り替えと AI モードからの切り替えでのみ有効にしています。これは、クリックまたはタップされたターゲットに応じて、ナビゲーションの @ 規則をプログラムで更新することで実現しました。
ビュー遷移 @ 規則を切り替える方法は次のとおりです。
let viewTransitionAtRule: HTMLElement | undefined;
const DISABLED_VIEW_TRANSITION = '@view-transition{navigation:none;}';
const ENABLED_VIEW_TRANSITION = '@view-transition{navigation:auto;}';
function getVtAtRule(): HTMLElement {
if (!viewTransitionAtRule) {
viewTransitionAtRule = document.createElement('style');
document.head.append(viewTransitionAtRule);
}
return viewTransitionAtRule;
}
function disableVt() {
getVtAtRule().textContent = DISABLED_VIEW_TRANSITION;
}
function enableVt() {
getVtAtRule().textContent = ENABLED_VIEW_TRANSITION;
}
ジャンクと合成アニメーション
ビュー遷移の疑似要素で自動生成されるアニメーションの一部が、古いデバイスでフレーム落ちを引き起こし、ユーザーに提供したいシームレスでクリーンなエクスペリエンスを損なう原因となっていました。アニメーションのパフォーマンスを向上させるため、コンポジタで実行できるアニメーション技術を使用してアニメーションを書き直しました。キーフレームを調べて、前後のスナップショットの疑似要素のディメンションを取得し、行列演算を使用してキーフレームを書き換えることで、この処理を実現しました。次の例は、各ビュー遷移疑似要素のアニメーションを取得する方法を示しています。
const pseudoElement = `::view-transition-group(${name})`;
const animation = document
.getAnimations()
.find(
(animation) =>
(animation.effect as KeyframeEffect)?.pseudoElement === pseudoElement,
);
パフォーマンスの高いビュー トランジション キーフレームの作成について詳しくは、ビュー トランジションの適用: スナップショットを含むブロックの処理をご覧ください。
その他の注意点
特に顕著な問題の 1 つは、view-transition-name
CSS プロパティで要素をタグ付けすると、スタッキング コンテキスト(ビュー遷移仕様: セクション 2.1.1)に影響することです。これは、コンテナ要素の z-index
の変更を必要とする複数のバグの原因でした。
また、デフォルトで要素に view-transition-name
値を追加しないようにすることもできます。Google 検索には多くの人が携わっています。チームが要素に設定した view-transition-name
値が、他のチームのユーザーが使用する値と競合しないように、ビュー遷移タイプを使用して、特定のビュー遷移タイプがアクティブな場合にのみ view-transition-name
プロパティを条件付きで追加しました。
ai-mode
のビュー遷移タイプがアクティブな場合にのみ、the-element
の view-transition-name
を要素に追加する CSS の例:
html:active-view-transition-type(ai-mode) {
#target {
view-transition-name: the-element;
}
}
すべてのビュー遷移にこれらの CSS ルールを適用したら、pageswap
イベントと pagereveal
イベントの間に、ナビゲーションの現在のビュー遷移タイプを動的に変更できます。
pageswap
イベント中にビューの切り替えタイプを ai-mode
に更新する例。
function updateViewTransitionTypes(
event: ViewTransitionEvent,
types: string[],
): void {
event.viewTransition.types.clear();
for (const type of types) {
event.viewTransition.types.add(type);
}
}
window.addEventListener(
'pageswap',
(e) => {
updateViewTransitionTypes(
e as ViewTransitionEvent,
['ai-mode'],
);
}
);
これにより、名前の衝突を防ぎ、AI モードの切り替えの一環としてスナップショットを撮る必要のない要素のスナップショットを不必要に撮ることを防ぎます。
最後に、スタッキング コンテキストの問題はビューの切り替え中にのみ発生します。これらの問題を解決するために、ビュー遷移を使用する際にこの問題を解決するためだけに元の要素の z-index を任意に変更するのではなく、生成された疑似要素の z-index をターゲットにすることができます。
::view-transition-group(the-element) {
z-index: 100;
}
次のステップ
Google 検索では、クロスドキュメント ビューの切り替えを使用する予定です。これには、Navigation API がクロスブラウザで利用可能になったら、その API との統合も含まれます。今後、どのようなサービスを構築していくか、ご期待ください。