<ViewTransition>
允许你为在 Transition 内更新的元素添加动画。
¥<ViewTransition>
lets you animate elements that update inside a Transition.
import {unstableViewTransition as ViewTransition} from 'react';
<ViewTransition>
<div>...</div>
</ViewTransition>
参考
¥Reference
<ViewTransition>
将元素封装在 <ViewTransition>
中,以便在它们在 转换 内部更新时为其设置动画。React 使用以下启发式方法来确定动画是否激活视图过渡:
¥Wrap elements in <ViewTransition>
to animate them when they update inside a Transition. React uses the following heuristics to determine if a View Transition activates for an animation:
-
enter
:如果ViewTransition
本身插入到此 Transition 中,则此 Transition 将会激活。¥
enter
: If aViewTransition
itself gets inserted in this Transition, then this will activate. -
exit
:如果ViewTransition
本身在此 Transition 中被删除,则这将被激活。¥
exit
: If aViewTransition
itself gets deleted in this Transition, then this will activate. -
update
:如果ViewTransition
内部有任何 React 正在进行的 DOM 突变(例如属性更改),或者ViewTransition
边界本身由于其直属兄弟组件而改变了大小或位置。如果有嵌套的ViewTransition
节点,则突变将应用于它们,而不是父节点。¥
update
: If aViewTransition
has any DOM mutations inside it that React is doing (such as a prop changing) or if theViewTransition
boundary itself changes size or position due to an immediate sibling. If there are nestedViewTransition
then the mutation applies to them and not the parent. -
share
:如果一个名为ViewTransition
的 Transition 位于已删除的子树中,而另一个同名的ViewTransition
位于同一 Transition 中已插入的子树中,则它们将组成一个共享元素 Transition,并从已删除的 Transition 动画到已插入的 Transition。¥
share
: If a namedViewTransition
is inside a deleted subtree and another namedViewTransition
with the same name is part of an inserted subtree in the same Transition, they form a Shared Element Transition, and it animates from the deleted one to the inserted one.
默认情况下,<ViewTransition>
动画采用平滑的交叉淡入淡出(浏览器默认视图转换)。你可以通过向 <ViewTransition>
组件提供 视图过渡类 来自定义动画。你可以为每种触发器自定义动画(参见 视图过渡样式)。
¥By default, <ViewTransition>
animates with a smooth cross-fade (the browser default view transition). You can customize the animation by providing a View Transition Class to the <ViewTransition>
component. You can customize animations for each kind of trigger (see Styling View Transitions).
深入研究
¥How does <ViewTransition>
work?
在底层,React 将 view-transition-name
应用于嵌套在 <ViewTransition>
组件内的最近 DOM 节点的内联样式。如果有多个像 <ViewTransition><div /><div /></ViewTransition>
这样的兄弟 DOM 节点,React 会在名称后添加后缀,使每个节点都具有唯一性,但从概念上讲,它们是同一个节点的一部分。React 不会立即应用这些参数,而只会在边界应该参与动画时才应用。
¥Under the hood, React applies view-transition-name
to inline styles of the nearest DOM node nested inside the <ViewTransition>
component. If there are multiple sibling DOM nodes like <ViewTransition><div /><div /></ViewTransition>
then React adds a suffix to the name to make each unique but conceptually they’re part of the same one. React doesn’t apply these eagerly but only at the time that boundary should participate in an animation.
React 会在后台自动调用 startViewTransition
,因此你永远不应该自己这样做。实际上,如果页面上有其他程序正在运行 ViewTransition,React 会中断它。因此,建议你使用 React 本身来协调这些操作。如果你过去有其他触发 ViewTransitions 的方式,我们建议你迁移到内置方式。
¥React automatically calls startViewTransition
itself behind the scenes so you should never do that yourself. In fact, if you have something else on the page running a ViewTransition React will interrupt it. So it’s recommended that you use React itself to coordinate these. If you had other ways of trigger ViewTransitions in the past, we recommend that you migrate to the built-in way.
如果其他 React ViewTransition 已在运行,则 React 将等待它们完成后再启动下一个。但是,重要的是,如果在第一个更新运行时发生了多个更新,则所有更新都将合并为一个更新。如果从 A 开始,则为 。然后与此同时,你会收到一个更新,转到 C,然后是 D。当第一个 A->B 动画完成后,下一个动画将从 B->D 开始。
¥If there are other React ViewTransitions already running then React will wait for them to finish before starting the next one. However, importantly if there are multiple updates happening while the first one is running, those will all be batched into one. If you start A->B. Then in the meantime you get an update to go to C and then D. When the first A->B animation finishes the next one will animate from B->D.
getSnapshotBeforeUpdate
生命周期将在 startViewTransition
之前调用,并且某些 view-transition-name
将同时更新。
¥The getSnapshotBeforeUpdate
life-cycle will be called before startViewTransition
and some view-transition-name
will update at the same time.
然后 React 调用 startViewTransition
。在 updateCallback
内部,React 将:
¥Then React calls startViewTransition
. Inside the updateCallback
, React will:
-
将其突变应用于 DOM 并调用 useInsertionEffects。
¥Apply its mutations to the DOM and invoke useInsertionEffects.
-
等待字体加载。
¥Wait for fonts to load.
-
调用 componentDidMount、componentDidUpdate、useLayoutEffect 和引用。
¥Call componentDidMount, componentDidUpdate, useLayoutEffect and refs.
-
等待任何待处理的导航完成。
¥Wait for any pending Navigation to finish.
-
然后 React 将测量布局的任何变化,以确定哪些边界需要动画。
¥Then React will measure any changes to the layout to see which boundaries will need to animate.
startViewTransition
的就绪 Promise 解析后,React 将还原 view-transition-name
。然后 React 将调用 onEnter
、onExit
、onUpdate
和 onShare
回调,以便手动编程控制动画。这将在内置默认值计算完毕后进行。
¥After the ready Promise of the startViewTransition
is resolved, React will then revert the view-transition-name
. Then React will invoke the onEnter
, onExit
, onUpdate
and onShare
callbacks to allow for manual programmatic control over the Animations. This will be after the built-in default ones have already been computed.
如果 flushSync
恰好处于此序列的中间,则 React 将跳过 Transition,因为它依赖于同步完成。
¥If a flushSync
happens to get in the middle of this sequence, then React will skip the Transition since it relies on being able to complete synchronously.
startViewTransition
的完成 Promise 解析后,React 将调用 useEffect
。这可以防止这些干扰动画的性能。然而,这并不能保证一定有效,因为如果在动画运行时再次发生 setState
,它仍然必须先调用 useEffect
来保持顺序保证。
¥After the finished Promise of the startViewTransition
is resolved, React will then invoke useEffect
. This prevents those from interfering with the performance of the Animation. However, this is not a guarantee because if another setState
happens while the Animation is running it’ll still have to invoke the useEffect
earlier to preserve the sequential guarantees.
属性
¥Props
默认情况下,<ViewTransition>
动画采用平滑的交叉淡入淡出。你可以使用以下属性自定义动画或指定共享元素过渡:
¥By default, <ViewTransition>
animates with a smooth cross-fade. You can customize the animation, or specify a shared element transition, with these props:
-
可选
enter
:字符串或对象。进入激活时应用的 视图过渡类。¥optional
enter
: A string or object. The View Transition Class to apply when enter is activated. -
可选
exit
:字符串或对象。退出激活时应用的 视图过渡类。¥optional
exit
: A string or object. The View Transition Class to apply when exit is activated. -
可选
update
:字符串或对象。更新激活时应用的 视图过渡类。¥optional
update
: A string or object. The View Transition Class to apply when an update is activated. -
可选
share
:字符串或对象。共享元素激活时应用的 视图过渡类。¥optional
share
: A string or object. The View Transition Class to apply when a shared element is activated. -
可选
default
:字符串或对象。未找到其他匹配的激活属性时使用的 视图过渡类。¥optional
default
: A string or object. The View Transition Class used when no other matching activation prop is found. -
可选
name
:字符串或对象。用于共享元素转换的视图转换的名称。如果未提供,React 将为每个视图转换使用唯一的名称,以防止意外的动画。¥optional
name
: A string or object. The name of the View Transition used for shared element transitions. If not provided, React will use a unique name for each View Transition to prevent unexpected animations.
回调函数
¥Callback
这些回调允许你使用 animate API 强制调整动画:
¥These callbacks allow you to adjust the animation imperatively using the animate APIs:
-
可选
onEnter
:一个功能。React 在 “enter” 动画之后调用onEnter
。¥optional
onEnter
: A function. React callsonEnter
after an “enter” animation. -
可选
onExit
:一个功能。React 在 “exit” 动画之后调用onExit
。¥optional
onExit
: A function. React callsonExit
after an “exit” animation. -
可选
onShare
:一个功能。React 在 “share” 动画之后调用onShare
。¥optional
onShare
: A function. React callsonShare
after a “share” animation. -
可选
onUpdate
:一个功能。React 在 “update” 动画之后调用onUpdate
。¥optional
onUpdate
: A function. React callsonUpdate
after an “update” animation.
每个回调接收的参数为:
¥Each callback receives as arguments:
-
element
:已添加动画效果的 DOM 元素。¥
element
: The DOM element that was animated. -
types
:动画中包含的 过渡类型。¥
types
: The Transition Types included in the animation.
视图过渡类
¥View Transition Class
View Transition Class 是 React 在 ViewTransition 激活时在过渡期间应用的 CSS 类名。它可以是字符串或对象。
¥The View Transition Class is the CSS class name(s) applied by React during the transition when the ViewTransition activates. It can be a string or an object.
-
string
:激活时添加到子元素上的class
。如果提供了'none'
,则不会添加任何类。¥
string
: theclass
added on the child elements when activated. If'none'
is provided, no class will be added. -
object
:添加到子元素上的类将是与addTransitionType
添加的 View Transition 类型匹配的关键。如果未找到匹配的类型,对象还可以指定要使用的default
。¥
object
: the class added on the child elements will be the key matching View Transition type added withaddTransitionType
. The object can also specify adefault
to use if no matching type is found.
'none'
值可用于阻止视图过渡因特定触发器而激活。
¥The value 'none'
can be used to prevent a View Transition from activating for a specific trigger.
视图过渡样式
¥Styling View Transitions
要自定义 <ViewTransition>
的动画,你可以为其中一个激活属性提供一个 View Transition 类。View Transition Class 是 React 在 ViewTransition 激活时应用于子元素的 CSS 类名。
¥To customize the animation for a <ViewTransition>
you can provide a View Transition Class to one of the activation props. The View Transition Class is a CSS class name that React applies to the child elements when the ViewTransition activates.
例如,要自定义 “enter” 动画,请为 enter
属性提供一个类名:
¥For example, to customize an “enter” animation, provide a class name to the enter
prop:
<ViewTransition enter="slide-in">
当 <ViewTransition>
激活 “enter” 动画时,React 会添加类名 slide-in
。然后你可以使用 视图过渡伪选择器 引用此类来构建可重用的动画:
¥When the <ViewTransition>
activates an “enter” animation, React will add the class name slide-in
. Then you can refer to this class using view transition pseudo selectors to build reusable animations:
::view-transition-group(.slide-in) {
}
::view-transition-old(.slide-in) {
}
::view-transition-new(.slide-in) {
}
未来,CSS 库可能会使用视图过渡类添加内置动画,以使其更易于使用。
¥In the future, CSS libraries may add built-in animations using View Transition Classes to make this easier to use.
注意事项
¥Caveats
-
默认情况下,
setState
会立即更新,不会激活<ViewTransition>
,只会更新封装在 转换 中的组件。你还可以使用<Suspense>
选择将 Transition 转换为 显示内容。¥By default,
setState
updates immediately and does not activate<ViewTransition>
, only updates wrapped in a Transition. You can also use<Suspense>
to opt-in to a Transition to reveal content. -
<ViewTransition>
创建一个可以移动、缩放和淡入淡出的图片。与你在 React Native 或 Motion 中看到的布局动画不同,这意味着并非其中的每个元素都会为其位置设置动画。与为每个部分单独设置动画相比,这可以带来更好的性能和更连续的感觉,动画也更流畅。但是,它也可能会使应该自行移动的元素失去连续性。因此,你可能需要手动添加更多<ViewTransition>
边界。¥
<ViewTransition>
creates an image that can be moved around, scaled and cross-faded. Unlike Layout Animations you may have seen in React Native or Motion, this means that not every individual Element inside of it animates its position. This can lead to better performance and a more continuous feeling, smooth animation compared to animating every individual piece. However, it can also lose continuity in things that should be moving by themselves. So you might have to add more<ViewTransition>
boundaries manually as a result. -
许多用户可能更喜欢页面上没有动画。在这种情况下,React 不会自动禁用动画。我们建议使用
@media (prefers-reduced-motion)
媒体查询来禁用动画,或者根据用户偏好降低动画效果。未来,CSS 库可能会将其内置到其预设中。¥Many users may prefer not having animations on the page. React doesn’t automatically disable animations for this case. We recommend that using the
@media (prefers-reduced-motion)
media query to disable animations or tone them down based on user preference. In the future, CSS libraries may have this built-in to their presets. -
目前,
<ViewTransition>
仅在 DOM 中起作用。我们正在努力添加对 React Native 和其他平台的支持。¥Currently,
<ViewTransition>
only works in the DOM. We’re working on adding support for React Native and other platforms.
用法
¥Usage
为元素添加进入/退出动画
¥Animating an element on enter/exit
当组件在过渡中添加或移除 <ViewTransition>
时,会触发进入/退出过渡:
¥Enter/Exit Transitions trigger when a <ViewTransition>
is added or removed by a component in a transition:
function Child() {
return <ViewTransition>Hi</ViewTransition>
}
function Parent() {
const [show, setShow] = useState();
if (show) {
return <Child />;
}
return null;
}
当调用 setShow
时,show
切换到 true
,并渲染 Child
组件。当在 startTransition
内部调用 setShow
时,如果 Child
在任何其他 DOM 节点之前渲染 ViewTransition
,则会触发 enter
动画。
¥When setShow
is called, show
switches to true
and the Child
component is rendered. When setShow
is called inside startTransition
, and Child
renders a ViewTransition
before any other DOM nodes, an enter
animation is triggered.
当 show
切换回 false
时,将触发 exit
动画。
¥When show
switches back to false
, an exit
animation is triggered.
import { unstable_ViewTransition as ViewTransition, useState, startTransition } from 'react'; import {Video} from "./Video"; import videos from "./data" function Item() { return ( <ViewTransition> <Video video={videos[0]}/> </ViewTransition> ); } export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} >{showItem ? '➖' : '➕'}</button> {showItem ? <Item /> : null} </> ); }
为共享元素添加动画
¥Animating a shared element
通常情况下,我们不建议为 <ViewTransition>
指定名称,而是让 React 自动为其指定名称。你可能需要指定名称的原因是,当一个组件树卸载而另一个组件树同时挂载时,可以在完全不同的组件之间进行动画处理。为了保持连续性。
¥Normally, we don’t recommend assigning a name to a <ViewTransition>
and instead let React assign it an automatic name. The reason you might want to assign a name is to animate between completely different components when one tree unmounts and another tree mounts at the same time. To preserve continuity.
<ViewTransition name={UNIQUE_NAME}>
<Child />
</ViewTransition>
当一个组件树卸载而另一个组件树挂载时,如果在卸载树和挂载树中存在同名组件,则它们会在两者上触发 “share” 动画。它会从卸载端动画到挂载端。
¥When one tree unmounts and another mounts, if there’s a pair where the same name exists in the unmounting tree and the mounting tree, they trigger the “share” animation on both. It animates from the unmounting side to the mounting side.
与退出/进入动画不同,它可以位于已删除/已挂载树的深处。如果 <ViewTransition>
也符合退出/进入的条件,则 “share” 动画优先。
¥Unlike an exit/enter animation this can be deeply inside the deleted/mounted tree. If a <ViewTransition>
would also be eligible for exit/enter, then the “share” animation takes precedence.
如果 Transition 首先卸载一侧,然后导致在最终挂载新名称之前显示 <Suspense>
回退,则不会发生共享元素转换。
¥If Transition first unmounts one side and then leads to a <Suspense>
fallback being shown before eventually the new name being mounted, then no shared element transition happens.
import { unstable_ViewTransition as ViewTransition, useState, startTransition } from "react"; import {Video, Thumbnail, FullscreenVideo} from "./Video"; import videos from "./data"; export default function Component() { const [fullscreen, setFullscreen] = useState(false); if (fullscreen) { return <FullscreenVideo video={videos[0]} onExit={() => startTransition(() => setFullscreen(false))} /> } return <Video video={videos[0]} onClick={() => startTransition(() => setFullscreen(true))} /> }
为列表中元素的重新排序添加动画
¥Animating reorder of items in a list
items.map(item => <Component key={item.id} item={item} />)
在重新排序列表时,如果不更新内容,如果列表中的每个 <ViewTransition>
位于 DOM 节点之外,则会触发 “update” 动画。类似于进入/退出动画。
¥When reordering a list, without updating the content, the “update” animation triggers on each <ViewTransition>
in the list if they’re outside a DOM node. Similar to enter/exit animations.
这意味着这将触发此 <ViewTransition>
上的动画:
¥This means that this will trigger the animation on this <ViewTransition>
:
function Component() {
return <ViewTransition><div>...</div></ViewTransition>;
}
import { unstable_ViewTransition as ViewTransition, useState, startTransition } from "react"; import {Video} from "./Video"; import videos from "./data"; export default function Component() { const [orderedVideos, setOrderedVideos] = useState(videos); const reorder = () => { startTransition(() => { setOrderedVideos((prev) => { return [...prev.sort(() => Math.random() - 0.5)]; }); }); }; return ( <> <button onClick={reorder}>🎲</button> <div className="listContainer"> {orderedVideos.map((video, i) => { return ( <ViewTransition key={video.title}> <Video video={video} /> </ViewTransition> ); })} </div> </> ); }
然而,这不会为每个单独的项目设置动画:
¥However, this wouldn’t animate each individual item:
function Component() {
return <div><ViewTransition>...</ViewTransition></div>;
}
任何父 <ViewTransition>
都会交叉淡入淡出。如果没有父 <ViewTransition>
,则在这种情况下没有动画。
¥Instead, any parent <ViewTransition>
would cross-fade. If there is no parent <ViewTransition>
then there’s no animation in that case.
import { unstable_ViewTransition as ViewTransition, useState, startTransition } from "react"; import {Video} from "./Video"; import videos from "./data"; export default function Component() { const [orderedVideos, setOrderedVideos] = useState(videos); const reorder = () => { startTransition(() => { setOrderedVideos((prev) => { return [...prev.sort(() => Math.random() - 0.5)]; }); }); }; return ( <> <button onClick={reorder}>🎲</button> <ViewTransition> <div className="listContainer"> {orderedVideos.map((video, i) => { return <Video video={video} key={video.title} />; })} </div> </ViewTransition> </> ); }
这意味着你可能希望避免在列表中使用封装元素,因为你想允许组件控制其自身的重新排序动画:
¥This means you might want to avoid wrapper elements in lists where you want to allow the Component to control its own reorder animation:
items.map(item => <div><Component key={item.id} item={item} /></div>)
如果其中一个项目更新为调整大小,从而导致其兄弟项目调整大小,则上述规则同样适用,它也会为其兄弟项目 <ViewTransition>
制作动画,但前提是它们是直系兄弟项目。
¥The above rule also applies if one of the items updates to resize, which then causes the siblings to resize, it’ll also animate its sibling <ViewTransition>
but only if they’re immediate siblings.
这意味着在更新过程中,会导致大量的重新布局,它不会单独为页面上的每个 <ViewTransition>
制作动画。这会导致动画效果过于嘈杂,从而分散对实际变化的注意力。因此,React 对于单个动画的触发时间更加保守。
¥This means that during an update, which causes a lot of re-layout, it doesn’t individually animate every <ViewTransition>
on the page. That would lead to a lot of noisy animations which distracts from the actual change. Therefore React is more conservative about when an individual animation triggers.
基于 Suspense 内容添加动画
¥Animating from Suspense content
与任何 Transition 一样,React 会在运行动画之前等待数据和新的 CSS(<link rel="stylesheet" precedence="...">
)。此外,ViewTransitions 还会在启动动画之前等待最多 500 毫秒以加载新字体,以避免它们稍后闪烁。出于同样的原因,ViewTransition 中封装的图片将等待图片加载完成。
¥Just like any Transition, React waits for data and new CSS (<link rel="stylesheet" precedence="...">
) before running the animation. In addition to this, ViewTransitions also wait up to 500ms for new fonts to load before starting the animation to avoid them flickering in later. For the same reason, an image wrapped in ViewTransition will wait for the image to load.
如果它位于新的 Suspense 边界实例内,则首先显示回退。Suspense 边界完全加载后,它会触发 <ViewTransition>
以动画方式显示内容。
¥If it’s inside a new Suspense boundary instance, then the fallback is shown first. After the Suspense boundary fully loads, it triggers the <ViewTransition>
to animate the reveal to the content.
目前,这仅适用于客户端过渡。未来,当服务器内容在初始加载期间暂停时,这还将为流式 SSR 的 Suspense 边界设置动画。
¥Currently, this only happens for client-side Transition. In the future, this will also animate Suspense boundary for streaming SSR when content from the server suspends during the initial load.
根据 <ViewTransition>
的放置位置,有两种方法可以为 Suspense 边界设置动画:
¥There are two ways to animate Suspense boundaries depending on where you place the <ViewTransition>
:
更新:
¥Update:
<ViewTransition>
<Suspense fallback={<A />}>
<B />
</Suspense>
</ViewTransition>
在这种情况下,当内容从 A 移动到 B 时,它将被视为 “update”,并在适当的情况下应用该类。A 和 B 都将获得相同的 view-transition-name,因此它们默认充当交叉淡入淡出效果。
¥In this scenario when the content goes from A to B, it’ll be treated as an “update” and apply that class if appropriate. Both A and B will get the same view-transition-name and therefore they’re acting as a cross-fade by default.
import { unstable_ViewTransition as ViewTransition, useState, startTransition, Suspense } from 'react'; import {Video, VideoPlaceholder} from "./Video"; import {useLazyVideoData} from "./data" function LazyVideo() { const video = useLazyVideoData(); return ( <Video video={video}/> ); } export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} >{showItem ? '➖' : '➕'}</button> {showItem ? ( <ViewTransition> <Suspense fallback={<VideoPlaceholder />}> <LazyVideo /> </Suspense> </ViewTransition> ) : null} </> ); }
进入/退出:
¥Enter/Exit:
<Suspense fallback={<ViewTransition><A /></ViewTransition>}>
<ViewTransition><B /></ViewTransition>
</Suspense>
在这种情况下,这是两个独立的 ViewTransition 实例,每个实例都有自己的 view-transition-name
。这将被视为 <A>
的 “exit” 和 <B>
的 “enter”。
¥In this scenario, these are two separate ViewTransition instances each with their own view-transition-name
. This will be treated as an “exit” of the <A>
and an “enter” of the <B>
.
你可以根据 <ViewTransition>
边界的位置实现不同的效果。
¥You can achieve different effects depending on where you choose to place the <ViewTransition>
boundary.
选择退出动画
¥Opting-out of an animation
有时,你会封装一个大型现有组件(例如整个页面),并希望为某些更新(例如更改主题)添加动画效果。但是,你不希望它在更新时选择让整个页面内的所有更新都交叉淡入淡出。尤其是在逐步添加更多动画时。
¥Sometimes you’re wrapping a large existing component, like a whole page, and you want to animate some updates, such as changing the theme. However, you don’t want it to opt-in all updates inside the whole page to cross-fade when they’re updating. Especially if you’re incrementally adding more animations.
你可以使用 “none” 类来选择退出动画。通过将子组件封装在 “none” 中,你可以在父组件仍然触发的情况下禁用它们的更新动画。
¥You can use the class “none” to opt-out of an animation. By wrapping your children in a “none” you can disable animations for updates to them while the parent still triggers.
<ViewTransition>
<div className={theme}>
<ViewTransition update="none">
{children}
</ViewTransition>
</div>
</ViewTransition>
这仅在主题更改时才会执行动画,而仅子元素更新时则不会执行动画。子项目仍然可以使用自己的 <ViewTransition>
再次选择加入,但至少它再次是手动的。
¥This will only animate if the theme changes and not if only the children update. The children can still opt-in again with their own <ViewTransition>
but at least it’s manual again.
自定义动画
¥Customizing animations
默认情况下,<ViewTransition>
包含浏览器默认的交叉淡入淡出。
¥By default, <ViewTransition>
includes the default cross-fade from the browser.
要自定义动画,你可以根据 <ViewTransition>
的激活方式,向 <ViewTransition>
组件提供属性,以指定要使用的动画。
¥To customize animations, you can provide props to the <ViewTransition>
component to specify which animations to use, based on how the <ViewTransition>
activates.
例如,我们可以减慢默认的淡入淡出动画:
¥For example, we can slow down the default cross fade animation:
<ViewTransition default="slow-fade">
<Video />
</ViewTransition>
并使用视图过渡类在 CSS 中定义慢淡入淡出效果:
¥And define slow-fade in CSS using view transition classes:
::view-transition-old(.slow-fade) {
animation-duration: 500ms;
}
::view-transition-new(.slow-fade) {
animation-duration: 500ms;
}
import { unstable_ViewTransition as ViewTransition, useState, startTransition } from 'react'; import {Video} from "./Video"; import videos from "./data" function Item() { return ( <ViewTransition default="slow-fade"> <Video video={videos[0]}/> </ViewTransition> ); } export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} >{showItem ? '➖' : '➕'}</button> {showItem ? <Item /> : null} </> ); }
除了设置 default
之外,你还可以为 enter
、exit
、update
和 share
动画提供配置。
¥In addition to setting the default
, you can also provide configurations for enter
, exit
, update
, and share
animations.
import { unstable_ViewTransition as ViewTransition, useState, startTransition } from 'react'; import {Video} from "./Video"; import videos from "./data" function Item() { return ( <ViewTransition enter="slide-in" exit="slide-out"> <Video video={videos[0]}/> </ViewTransition> ); } export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }} >{showItem ? '➖' : '➕'}</button> {showItem ? <Item /> : null} </> ); }
使用类型自定义动画
¥Customizing animations with types
当特定激活触发器激活特定过渡类型时,你可以使用 addTransitionType
API 为子元素添加类名。这允许你为每种类型的过渡自定义动画。
¥You can use the addTransitionType
API to add a class name to the child elements when a specific transition type is activated for a specific activation trigger. This allows you to customize the animation for each type of transition.
例如,要自定义所有前进和后退导航的动画:
¥For example, to customize the animation for all forward and backward navigations:
<ViewTransition default={{
'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”。
¥When the ViewTransition activates a “navigation-back” animation, React will add the class name “slide-right”. When the ViewTransition activates a “navigation-forward” animation, React will add the class name “slide-left”.
未来,路由和其他库可能会添加对标准视图过渡类型和样式的支持。
¥In the future, routers and other libraries may add support for standard view-transition types and styles.
import { unstable_ViewTransition as ViewTransition, unstable_addTransitionType as addTransitionType, useState, startTransition, } from "react"; import {Video} from "./Video"; import videos from "./data" function Item() { return ( <ViewTransition enter={ { "add-video-back": "slide-in-back", "add-video-forward": "slide-in-forward" } } exit={ { "remove-video-back": "slide-in-forward", "remove-video-forward": "slide-in-back" } }> <Video video={videos[0]}/> </ViewTransition> ); } export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <div className="button-container"> <button onClick={() => { startTransition(() => { if (showItem) { addTransitionType("remove-video-back") } else { addTransitionType("add-video-back") } setShowItem((prev) => !prev); }); }} >⬅️</button> <button onClick={() => { startTransition(() => { if (showItem) { addTransitionType("remove-video-forward") } else { addTransitionType("add-video-forward") } setShowItem((prev) => !prev); }); }} >➡️</button> </div> {showItem ? <Item /> : null} </> ); }
构建支持 View Transition 的路由
¥Building View Transition enabled routers
React 等待任何待处理的导航完成,以确保滚动恢复在动画内进行。如果 React 上的导航被阻塞,则你的路由必须在 useLayoutEffect
中解除阻塞,因为 useEffect
会导致死锁。
¥React waits for any pending Navigation to finish to ensure that scroll restoration happens within the animation. If the Navigation is blocked on React, your router must unblock in useLayoutEffect
since useEffect
would lead to a deadlock.
如果 startTransition
是从旧版 popstate 事件启动的,例如在 “back” 导航期间,则它必须同步完成以确保滚动和表单恢复正常工作。这与运行视图过渡动画冲突。因此,React 会跳过 popstate 中的动画。因此,动画不会针对返回按钮运行。你可以通过升级路由以使用导航 API 来解决此问题。
¥If a startTransition
is started from the legacy popstate event, such as during a “back”-navigation then it must finish synchronously to ensure scroll and form restoration works correctly. This is in conflict with running a View Transition animation. Therefore, React will skip animations from popstate. Therefore animations won’t run for the back button. You can fix this by upgrading your router to use the Navigation API.
故障排除
¥Troubleshooting
我的 <ViewTransition>
没有激活
¥My <ViewTransition>
is not activating
<ViewTransition>
仅在放置在任何 DOM 节点之前时才会激活:
¥<ViewTransition>
only activates if it is placed is before any DOM node:
function Component() {
return (
<div>
<ViewTransition>Hi</ViewTransition>
</div>
);
}
要修复此问题,请确保 <ViewTransition>
位于任何其他 DOM 节点之前:
¥To fix, ensure that the <ViewTransition>
comes before any other DOM nodes:
function Component() {
return (
<ViewTransition>
<div>Hi</div>
</ViewTransition>
);
}
我收到一条错误消息:“有两个同名的 <ViewTransition name=%s>
组件同时挂载。”
¥I’m getting an error “There are two <ViewTransition name=%s>
components with the same name mounted at the same time.”
当同时挂载两个具有相同 name
的 <ViewTransition>
组件时,会发生此错误:
¥This error occurs when two <ViewTransition>
components with the same name
are mounted at the same time:
function Item() {
// 🚩 All items will get the same "name".
return <ViewTransition name="item">...</ViewTransition>;
}
function ItemList({items}) {
return (
<>
{item.map(item => <Item key={item.id} />)}
</>
);
}
这将导致视图转换出错。在开发环境中,React 会检测到此问题并将其暴露出来并记录两个错误:
¥This will cause the View Transition to error. In development, React detects this issue to surface it and logs two errors:
要修复此问题,请确保整个应用中一次只安装一个同名的 <ViewTransition>
,方法是确保 name
是唯一的,或者在名称中添加 id
:
¥To fix, ensure that there’s only one <ViewTransition>
with the same name mounted at a time in the entire app by ensuring the name
is unique, or adding an id
to the name:
function Item({id}) {
// ✅ All items will get the same "name".
return <ViewTransition name={`item-${id}`}>...</ViewTransition>;
}
function ItemList({items}) {
return (
<>
{item.map(item => <Item key={item.id} item={item} />)}
</>
);
}