Как Google создала взаимосвязанный опыт для своего нового режима искусственного интеллекта с помощью переходов между представлениями

Опубликовано: 28 августа 2025 г.

Поиск Google обладает одним из самых больших охватов в мире, поэтому изменения в пользовательском интерфейсе могут повлиять на миллиарды пользователей. Мы давно мечтали о веб-интерфейсе, который был бы более современным и похожим на приложение. Когда началась разработка режима AI , мы хотели создать для наших пользователей такой интерфейс, чтобы переход из стандартного поиска в режим AI был плавным и органичным. Узнав о переходах между документами, мы поняли, что это идеальное дополнение к этой функции. В этом примере мы расскажем о том, что узнали, добавив функцию перехода одновременно с запуском режима AI.

Запись поиска в Google Search, переключение из результатов поиска в режим искусственного интеллекта. Переход осуществляется с помощью переходов между окнами.

Переходы между окнами просмотра разных документов кардинально меняют ситуацию в плане нативных инструментов браузера, и мы с нетерпением ждём, как это повлияет на развитие веб-технологий в будущем.

Browser Support

  • Chrome: 126.
  • Edge: 126.
  • Firefox: не поддерживается.
  • Сафари: 18.2.

Source

Изменение существующего положения вещей

Поиск Google предъявляет строгие и консервативные требования к поддержке браузеров. Как правило, использование функций с ограниченной доступностью было запрещено. Что касается переходов между окнами просмотра документов, мы обнаружили, что полифил нецелесообразен, главным препятствием является отсутствие API для создания снимков пикселей, а клонирование всего окна просмотра вызывает серьезные проблемы с производительностью. Поэтому использование этой функции в качестве прогрессивного улучшения стало лучшим способом запуска вместе с режимом искусственного интеллекта. Поскольку анимации, создаваемые переходами между окнами просмотра, напрямую не влияют на функциональность веб-сайта, для неподдерживаемого трафика они просто отключаются, что уже и так было текущим состоянием производства без анимаций переходов.

Сначала мы протестировали эту стратегию поэтапного улучшения на наших внутренних пользователях. Это позволило нам получить первые отзывы, которые были в подавляющем большинстве положительными. Полученные отзывы также выявили ошибки, в том числе проблемы с производительностью и непредвиденные взаимодействия с другими функциями, такими как перекрывающиеся контексты наложения.

Мы убедились в эффективности этой стратегии, и я считаю, что в будущем мы будем применять её и к другим новым функциям браузера.

Трудности, с которыми мы столкнулись, и способы их решения.

Задержка, блокировка рендеринга и сторожевые таймеры

В целом, дополнительная задержка, возникающая при переходах между окнами MPA, незначительна в 99% случаев использования, особенно на современном оборудовании. Однако Google Search предъявляет чрезвычайно высокие требования к задержке, и мы стремимся создавать пользовательский опыт, который хорошо работает на всех устройствах. Для нас важны даже несколько дополнительных миллисекунд, поэтому нам пришлось инвестировать в разработку правильной реализации переходов между окнами разных документов без ущерба для пользовательского опыта.

Блокировка рендеринга — это техника, хорошо сочетающаяся с переходами между представлениями документа. Псевдоэлементные снимки входящего документа могут отображать только уже отрисованный контент. Поэтому для анимации контента из входящего документа необходимо блокировать рендеринг до тех пор, пока не будет отрисован целевой элемент, который вы хотите анимировать . Для этого используйте атрибут blocking у ` HTMLLinkElement . Блокировка рендеринга имеет свои недостатки, поскольку ожидание элемента, находящегося в конце дерева 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();
}

Ограничения покрытия

Ещё одна проблема, с которой мы столкнулись, заключается в том, что правило navigation: auto для переходов между документами применяется на глобальном уровне внутри документа. Встроенного способа ограничить включение переходов между документами только определёнными целями клика. Поскольку это такое масштабное изменение, мы не смогли включить переходы между документами для 100% навигации в Google Поиске. Нам нужен был способ динамически включать или отключать переходы между документами в зависимости от того, с какой функцией взаимодействует пользователь. В нашем случае мы включили их только при переключении между режимами AI Mode и AI Mode. Мы сделали это, программно обновляя правило навигации в зависимости от того, на какую цель был нажат или коснулся пользователь.

Переключать режим перехода между окнами с помощью правила можно следующим образом:

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,
  );

Подробнее о создании эффективных ключевых кадров для переходов между представлениями читайте в статье «Применение переходов между представлениями: работа с блоком, содержащим снимок» .

На что ещё следует обратить внимание

Одна из наиболее существенных проблем заключается в том, что добавление к элементам CSS-свойства view-transition-name влияет на контекст наложения ( спецификация переходов между представлениями: раздел 2.1.1 ). Это стало причиной множества ошибок, потребовавших изменения z-index элементов-контейнеров.

Ещё один важный момент: возможно, вам не захочется добавлять значения view-transition-name к элементам по умолчанию. Над Google Поиском работает много людей. Чтобы предотвратить конфликт значений view-transition-name наша команда устанавливает для элементов, со значениями, которые могут использовать сотрудники других команд, мы использовали типы переходов представления , чтобы добавлять свойство view-transition-name только при условии активности определённого типа перехода представления.

Пример CSS-кода для добавления имени the-element , соответствующего view-transition-name ), только тогда, когда активен тип перехода ( ai-mode :

html:active-view-transition-type(ai-mode) {
  #target {
    view-transition-name: the-element;
  }
}

После того как вы настроите эти правила CSS для всех переходов между представлениями, вы сможете динамически изменять текущий тип перехода для любой навигации во время событий pageswap и pagereveal .

Пример изменения типа перехода представления на ai-mode во время события pageswap .

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'],
    );
  }
);

Таким образом, мы предотвращаем конфликты имен и не создаем ненужные снимки элементов, которые не нуждаются в съемке, при переходе в режим ИИ и обратно.

Наконец, любые проблемы с контекстом наложения будут возникать только во время перехода между представлениями. Для решения этих проблем мы можем воздействовать на z-индексы сгенерированных псевдоэлементов, вместо того чтобы произвольно изменять z-индексы исходных элементов исключительно для решения этой проблемы при использовании переходов между представлениями.

::view-transition-group(the-element) {
  z-index: 100;
}

Что дальше?

Мы планируем использовать переходы между документами в Google Поиске, включая интеграцию с API навигации, как только она станет доступна во всех браузерах. Следите за обновлениями, чтобы узнать, что мы будем создавать дальше!