Под капотом React применяет view-transition-name к инлайновым стилям ближайшего DOM-узла внутри компонента <ViewTransition>. Если соседних DOM-узлов несколько, как в <ViewTransition></ViewTransition>, React добавляет к имени суффикс, чтобы каждое было уникальным, но концептуально они остаются частью одного перехода. React не применяет их заранее, а только в тот момент, когда граница должна участвовать в анимации.
React сам автоматически вызывает startViewTransition за кадром, поэтому вызывать его самостоятельно не нужно. Более того, если на странице что-то ещё уже запускает ViewTransition, React прервёт его. Поэтому такие переходы лучше координировать самим React. Если раньше вы запускали ViewTransition другими способами, рекомендуем перейти на встроенный способ.
Если другие ViewTransition в React уже выполняются, React дождётся их завершения, прежде чем запустить следующий. При этом важно: если пока выполняется первый, происходит несколько обновлений, все они объединяются в одно. Если начат переход A->B, а тем временем приходит обновление к C, а затем к D, то после окончания первой анимации A->B следующая анимирует переход от B к D.
Метод жизненного цикла getSnapshotBeforeUpdate вызывается до startViewTransition, и часть view-transition-name обновляется в тот же момент.
Затем React вызывает startViewTransition. Внутри updateCallback React:
Применит свои мутации к DOM и вызовет useInsertionEffect.
Дождётся загрузки шрифтов.
Вызовет componentDidMount, componentDidUpdate, useLayoutEffect и рефы.
Дождётся завершения любой ожидающей навигации (Navigation).
Измерит изменения компоновки, чтобы определить, какие границы нужно анимировать.
После того как промис ready у startViewTransition выполнится, React вернёт view-transition-name обратно. Затем React вызовет колбэки onEnter, onExit, onUpdate и onShare, чтобы можно было управлять анимациями программно вручную. Это произойдёт после того, как встроенные анимации по умолчанию уже будут вычислены.
Если в середину этой последовательности попадёт flushSync, React пропустит переход (Transition): он должен завершиться синхронно.
После того как промис finished у startViewTransition выполнится, React вызовет useEffect. Так эффекты не мешают производительности анимации. Это не абсолютная гарантия: если во время анимации произойдёт ещё один setState, React всё равно вызовет useEffect раньше, чтобы сохранить гарантии последовательности.
необязательныйname: Строка или объект. Имя перехода вида (View Transition) для переходов общего элемента. Если не указано, React использует уникальное имя для каждого перехода вида, чтобы избежать неожиданных анимаций.
Используйте name только для переходов общего элемента. Для всех остальных анимаций React автоматически создаёт уникальное имя, чтобы избежать неожиданных анимаций.
По умолчанию setState обновляет состояние сразу и не активирует <ViewTransition>. Переход вида активируют только обновления, обёрнутые в переход, <Suspense> или useDeferredValue.
<ViewTransition> создаёт изображение, которое можно перемещать, масштабировать и плавно перекрёстно затухать (cross-fade). В отличие от анимаций компоновки в React Native или Motion, это означает, что не каждый отдельный элемент внутри анимирует свою позицию. Так можно получить лучшую производительность и более непрерывную, плавную анимацию, чем при анимации каждой части по отдельности. При этом можно потерять непрерывность у того, что должно двигаться самостоятельно. Тогда дополнительные границы <ViewTransition> придётся добавить вручную.
Сейчас <ViewTransition> работает только в DOM. Поддержка React Native и других платформ в разработке.
enter: срабатывает, если ViewTransition — первый компонент, вставленный в этом переходе.
exit: срабатывает, если ViewTransition — первый компонент, удалённый в этом переходе.
update: срабатывает, если внутри ViewTransition есть мутации DOM, которые выполняет React (например, изменился проп), или если сама граница ViewTransition меняет размер или позицию из-за непосредственного соседа. Если есть вложенные ViewTransition, мутация относится к ним, а не к родителю.
share: если именованный ViewTransition находится внутри удалённого поддерева, а другой именованный ViewTransition с тем же именем входит во вставленное поддерево в том же переходе, они образуют переход общего элемента, и анимация идёт от удалённого к вставленному.
По умолчанию <ViewTransition> анимируется плавным перекрёстным затуханием (cross-fade) — это переход вида браузера по умолчанию.
Многие пользователи предпочитают, чтобы на странице не было анимаций. React не отключает анимации в этом случае сам.
Рекомендуем всегда использовать медиазапрос @media (prefers-reduced-motion), чтобы отключать анимации или смягчать их с учётом предпочтений пользователя.
В будущем CSS-библиотеки могут встроить это в свои пресеты.
Значения класса перехода вида могут быть такими: - auto: значение по умолчанию. Используется анимация браузера по умолчанию. - none: отключает анимации этого типа. - <classname>: имя собственного CSS-класса для настройки переходов вида.
Значение-объект — это объект со строковыми ключами и значением auto, none или собственным className: - {[type]: value}: применяет value, если анимация соответствует типу перехода. - {default: value}: значение по умолчанию, если ни один тип перехода не совпал.
Например, ViewTransition можно задать так:
1 2 3 4 5 6 7 8 91011121314
<ViewTransition/* turn off any animation not defined below */default="none"enter={{/* apply slide-in for Transition Type `forward` */"forward":'slide-in',/* otherwise use the browser default animation */"default":'auto'}}/* use the browser default for exit animations*/exit="auto"/* apply a custom `cross-fade` class for updates */update="cross-fade">
На каждый <ViewTransition> в одном переходе срабатывает только одно событие. onShare имеет приоритет над onEnter и onExit.
Каждое событие должно возвращать функцию очистки. Она вызывается, когда переход вида завершается, и позволяет отменить анимации или освободить связанные с ними ресурсы.
Во многих ранних примерах View Transitions в интернете задают view-transition-name и стилизуют его селекторами ::view-transition-...(my-name). Для стилизации лучше использовать класс перехода вида.
Чтобы настроить анимацию <ViewTransition>, передайте класс перехода вида в один из пропсов активации. Класс перехода вида — это имя CSS-класса, которое React применяет к дочерним элементам, когда ViewTransition активируется.
Например, чтобы настроить анимацию «enter», передайте имя класса в проп enter:
1
<ViewTransitionenter="slide-in">
Когда <ViewTransition> активирует анимацию «enter», React добавит класс slide-in. Затем на этот класс можно ссылаться через псевдоселекторы перехода вида и собирать переиспользуемые анимации:
Когда вызывается setShow, show становится true, и рендерится компонент Child. Если setShow вызван внутри startTransition, а Child рендерит ViewTransition раньше любых других DOM-узлов, запускается анимация enter.
Когда show снова становится false, запускается анимация exit.
Если нужно анимировать появление и исчезновение компонента, сохраняя его состояние, или заранее отрендерить содержимое для анимации, используйте <Activity>. Когда <ViewTransition> внутри <Activity> становится видимым, активируется анимация enter. Когда он скрывается, активируется анимация exit:
В этом примере у Counter есть счётчик с внутренним состоянием. Увеличьте счётчик, скройте его, а затем покажите снова. Значение счётчика сохраняется, пока боковая панель анимируется при появлении и исчезновении:
Обычно имя для <ViewTransition> лучше не задавать и позволить React назначить его автоматически. Имя нужно, чтобы анимировать переход между совершенно разными компонентами, когда одно дерево размонтируется, а другое монтируется в тот же момент, и сохранить непрерывность.
Когда одно дерево размонтируется, а другое монтируется, пара с одним и тем же именем в размонтируемом и монтируемом деревьях запускает анимацию «share» на обоих. Она идёт от размонтируемой стороны к монтируемой.
В отличие от анимации выхода и входа, такая пара может находиться глубоко внутри удалённого или смонтированного дерева. Если <ViewTransition> также подходит для выхода и входа, приоритет у анимации «share».
Если переход сначала размонтирует одну сторону, затем покажется фолбэк <Suspense>, и только потом смонтируется новое имя, переход общего элемента не выполняется.
Если смонтированная или размонтированная сторона пары находится за пределами области просмотра, пара не образуется. Так элемент не влетает в область просмотра и не вылетает из неё при прокрутке. Вместо этого он обрабатывается как обычный самостоятельный вход или выход.
Этого не происходит, если один и тот же экземпляр компонента меняет позицию: тогда запускается «update». Такие анимации идут независимо от того, находится ли одна из позиций за пределами области просмотра.
Есть известный случай: если глубоко вложенный размонтированный <ViewTransition> находится внутри области просмотра, а смонтированная сторона — нет, размонтированная сторона анимируется как собственная анимация «exit», даже когда она глубоко вложена, а не как часть анимации родителя.
Подводный камень
Во всём приложении одновременно может быть смонтирован только один элемент с одним и тем же именем. Поэтому для имени нужны уникальные пространства имён, чтобы избежать конфликтов. Для этого удобно завести константу в отдельном модуле и импортировать её.
При перестановке списка без изменения содержимого анимация «update» запускается на каждом <ViewTransition> в списке, если они находятся вне DOM-узла. Это похоже на анимации входа и выхода.
Значит, анимация запустится на таком <ViewTransition>:
То же правило действует, если один из элементов обновляется и меняет размер, из-за чего соседи тоже меняют размер: соседний <ViewTransition> тоже анимируется, но только если это непосредственные соседи.
Значит, во время обновления с большой перекомпоновкой React не анимирует каждый <ViewTransition> на странице по отдельности. Иначе получилось бы много шумных анимаций, которые отвлекают от самого изменения. Поэтому React осторожнее выбирает, когда запускать отдельную анимацию.
Подводный камень
При перестановке списков важно правильно использовать ключи, чтобы сохранять идентичность. Может показаться, что для анимации перестановки подойдут «name» и переходы общего элемента, но они не запустятся, если одна сторона окажется за пределами области просмотра. При анимации перестановки часто как раз нужно показать, что элемент ушёл на позицию за пределами области просмотра.
Как и любой переход, React ждёт данные и новый CSS (<link rel="stylesheet" precedence="...">), прежде чем запустить анимацию. Кроме того, ViewTransition ждёт до 500 мс загрузки новых шрифтов, прежде чем начать анимацию, чтобы они не мигнули позже. По той же причине изображение внутри ViewTransition дождётся загрузки этого изображения. Примеры ожидания шрифта и ожидания изображения есть на странице Suspense.
Если это происходит внутри нового экземпляра границы приостановки, сначала показывается фолбэк. Когда граница приостановки полностью загрузится, она запускает <ViewTransition>, чтобы анимировать появление содержимого.
Границы приостановки можно анимировать двумя способами в зависимости от того, куда поместить <ViewTransition>:
В этом сценарии, когда содержимое переходит от A к B, это считается «update», и при необходимости применяется соответствующий класс. И A, и B получают одно и то же view-transition-name, поэтому по умолчанию они работают как cross-fade.
Иногда вы оборачиваете большой существующий компонент, например целую страницу, и хотите анимировать отдельные обновления, такие как смена темы. При этом все обновления внутри страницы не должны сами включаться в cross-fade. Это особенно важно, когда анимации добавляются постепенно.
Чтобы отказаться от анимации, используйте класс «none». Если обернуть дочерние элементы в «none», анимации их обновлений отключаются, а родитель по-прежнему запускается.
Анимация произойдёт только при смене темы. Обновление одних дочерних элементов её не запустит. Дочерние элементы по-прежнему могут включиться снова через собственный <ViewTransition>, но это уже делается вручную.
По умолчанию <ViewTransition> использует стандартное перекрёстное затухание (cross-fade) браузера.
Чтобы настроить анимации, передайте пропсы компоненту <ViewTransition> и укажите, какие анимации использовать, в зависимости от того, как активируется <ViewTransition>.
Например, стандартную анимацию cross-fade можно замедлить:
С помощью API addTransitionType можно добавить имя класса дочерним элементам, когда для определённого триггера активации включается определённый тип перехода. Так анимацию можно настроить для каждого типа перехода.
Например, чтобы настроить анимацию всех переходов вперёд и назад:
1 2 3 4 5 6 7 8 9101112
<ViewTransitiondefault={{'navigation-back':'slide-right','navigation-forward':'slide-left',}}><div>...</div></ViewTransition>;// in your router:startTransition(()=>{addTransitionType('navigation-'+navigationType);});
Когда ViewTransition активирует анимацию «navigation-back», React добавит класс «slide-right». Когда ViewTransition активирует анимацию «navigation-forward», React добавит класс «slide-left».
В будущем маршрутизаторы и другие библиотеки могут добавить поддержку стандартных типов и стилей view-transition.
Классы перехода вида задают анимации в CSS, но иногда нужен императивный контроль. Колбэки onEnter, onExit, onUpdate и onShare дают прямой доступ к псевдоэлементам перехода вида, чтобы анимировать их через Web Animations API.
Каждый колбэк получает instance со свойствами .old и .new — это псевдоэлементы перехода вида. На них можно вызвать .animate() так же, как на DOM-элементе:
React ждёт завершения любой ожидающей навигации (Navigation), чтобы восстановление прокрутки произошло внутри анимации. Если навигация заблокирована на React, маршрутизатор должен снять блокировку в useLayoutEffect: useEffect приведёт к взаимной блокировке.
Если startTransition запущен из устаревшего события popstate, например при навигации «назад», он должен завершиться синхронно, чтобы правильно восстановились прокрутка и форма. Это конфликтует с анимацией перехода вида. Поэтому React пропускает анимации из popstate, и для кнопки «Назад» они не выполняются. Исправление — обновить маршрутизатор до Navigation API.
Ошибка «There are two <ViewTransition name=%s> components with the same name mounted at the same time.»¶
Эта ошибка возникает, когда одновременно смонтированы два компонента <ViewTransition> с одним и тем же name:
1 2 3 4 5 6 7 8 91011121314
functionItem(){// 🚩 All items will get the same "name".return<ViewTransitionname="item">...</ViewTransition>;}functionItemList({items}){return(<>{items.map((item)=>(<Itemkey={item.id}/>))}</>);}
Из-за этого переход вида завершится ошибкой. В режиме разработки React обнаруживает проблему, чтобы показать её, и пишет в журнал две ошибки:
<ConsoleLogLine level="error">
There are two `<ViewTransition name=%s>` components with the same name mounted at the same time. This is not supported and will cause View Transitions to error. Try to use a more unique name e.g. by using a namespace prefix and adding the id of an item to the name.
{' '}at Item
{' '}at ItemList
</ConsoleLogLine>
<ConsoleLogLine level="error">
The existing `<ViewTransition name=%s>` duplicate has this stack trace.
{' '}at Item
{' '}at ItemList
</ConsoleLogLine>
Чтобы это исправить, во всём приложении одновременно должен быть смонтирован только один <ViewTransition> с таким именем: сделайте name уникальным или добавьте к имени id:
1 2 3 4 5 6 7 8 91011121314
functionItem({id}){// ✅ All items will get a unique name.return<ViewTransitionname={`item-${id}`}>...</ViewTransition>;}functionItemList({items}){return(<>{items.map((item)=>(<Itemkey={item.id}item={item}/>))}</>);}