随着最近的 Safari 更新,网页动画 API (WAAPI) 现在在所有现代浏览器(IE 除外)中都无需标志即可支持。 这里有一个 方便的 Pen,您可以在其中检查浏览器支持哪些功能。WAAPI 是进行动画(需要在 JavaScript 中完成)的一种不错的方法,因为它属于原生,这意味着它不需要额外的库即可工作。如果您完全不了解 WAAPI,这里有一个 由 Dan Wilson 编写的非常好的介绍。
动画中最有效的方法之一是 FLIP。FLIP 需要一些 JavaScript 代码才能完成其工作。
让我们看看使用 WAAPI、FLIP 以及将所有这些集成到 React 中的交叉点。但我们先从没有 React 的情况开始,然后再进行到 React。
FLIP 和 WAAPI
FLIP 动画 由 WAAPI 大大简化了!
快速复习一下 FLIP:最大的想法是,您首先将元素放置在您希望它最终到达的位置。接下来,应用转换以将其移动到起始位置。然后取消应用这些转换。
动画转换非常高效,因此 FLIP 非常高效。在 WAAPI 之前,我们必须直接操作元素的样式以设置转换,并等待下一帧才能将其取消设置/反转
// FLIP Before the WAAPI
el.style.transform = `translateY(200px)`;
requestAnimationFrame(() => {
el.style.transform = '';
});
许多库都是基于这种方法构建的。 但是,这种方法存在一些问题
- 一切都感觉像是巨大的 hack。
- 反转 FLIP 动画极其困难。虽然 CSS 转换在删除类后会“免费”反转,但这在这种情况并非如此。在之前的 FLIP 仍在运行时开始新的 FLIP 会导致故障。反转需要使用
getComputedStyles
解析转换矩阵并使用它来计算当前尺寸,然后设置新的动画。 - 高级动画几乎不可能。例如,为了防止缩放父元素的子元素变形,我们需要在每一帧都访问当前缩放值。这只能通过解析转换矩阵来完成。
- 浏览器有很多陷阱。例如,有时 要在 Firefox 中完美地执行 FLIP 动画需要调用
requestAnimationFrame
两次
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transform = '';
});
});
使用 WAAPI 时,我们不会遇到这些问题。反转可以通过 reverse
函数轻松完成。子元素的反向缩放也是可能的。而且,当出现错误时,很容易找出确切的原因,因为我们只使用简单的函数,比如animate
和 reverse
,而不是像requestAnimationFrame
方法那样逐一梳理。
以下是 WAAPI 版本的概述
el.classList.toggle('someclass');
const keyframes = /* Calculate the size/position diff */;
el.animate(keyframes, 2000);
FLIP 和 React
要了解 FLIP 动画在 React 中的工作原理,必须了解它们如何,最重要的是为什么它们在纯 JavaScript 中有效。回忆一下 FLIP 动画的结构

所有带有紫色背景的内容都必须在渲染的“绘制”步骤之前完成。否则,我们会看到新的样式短暂闪烁,这不好。在 React 中情况会变得更加复杂,因为所有 DOM 更新都由我们完成。
FLIP 动画的神奇之处在于,元素在浏览器有机会绘制之前就会进行转换。那么如何在 React 中确定“绘制之前”的时刻呢?
认识一下 useLayoutEffect
hook。如果您想知道它有什么用,这就是它!我们在回调中传递的任何内容都将在 DOM 更新之后但在绘制之前同步执行。换句话说,这是设置 FLIP 的绝佳位置!
让我们做一些 FLIP 技术非常擅长的:动画化 DOM 位置。如果我们要动画化元素如何从一个 DOM 位置移动到另一个 DOM 位置,CSS 无能为力。(想象一下,在待办事项列表中完成一项任务并将其移至“已完成”任务列表,就像在下面的 Pen 中单击项目时一样。)
让我们看一下最简单的示例。单击下面 Pen 中的两个正方形中的任何一个,它们都会交换位置。如果没有 FLIP,它会立即发生。
那里发生了很多事情。注意所有工作是如何在生命周期挂钩回调中完成的:useEffect
和 useLayoutEffect
。让它有点令人困惑的是,FLIP 动画的时间轴不是从代码本身就能看出来的,因为它跨越了两次 React 渲染。以下是 React FLIP 动画的结构,以显示操作的不同顺序

虽然useEffect
总是运行在useLayoutEffect
和浏览器绘制之后,但重要的是我们必须在第一次渲染后缓存元素的位置和大小。我们没有机会在第二次渲染时这样做,因为useLayoutEffect
运行在所有 DOM 更新之后。但该过程本质上与 vanilla FLIP 动画相同。
注意事项
像大多数事情一样,在 React 中使用 FLIP 时,有一些注意事项需要考虑。
保持在 100 毫秒以内
FLIP 动画是计算。计算需要时间,并且在您能够显示流畅的 60fps 转换之前,您需要做相当多的工作。如果延迟低于 100 毫秒,人们不会注意到,所以确保一切都低于这个值。DevTools 中的“性能”选项卡是检查此问题的理想位置。

不必要的渲染
我们不能使用 useState 来缓存大小、位置和动画对象,因为每次setState
都会导致不必要的渲染,从而减慢应用程序的速度。在最坏的情况下,甚至会导致错误。尝试改为使用useRef
,将其视为可以被修改而不渲染任何内容的对象。
布局抖动
避免反复触发浏览器布局。在 FLIP 动画的上下文中,这意味着避免循环遍历元素并使用getBoundingClientRect
读取它们的位置,然后立即使用 animate 对它们进行动画化。尽可能地批处理“读取”和“写入”。这将使动画极其流畅。
动画取消
尝试在 之前的演示 中随机单击正方形,当它们移动时,然后再单击一次,当它们停止时。您会看到故障。在现实生活中,用户会在元素移动时与它们进行交互,因此值得确保它们被取消、暂停并流畅地更新。
但是,并非所有动画都可以使用reverse
反转。有时,我们希望它们停止,然后移动到新的位置(就像随机打乱元素列表时一样)。在这种情况下,我们需要
- 获取正在移动元素的大小/位置
- 完成当前动画
- 计算新的尺寸和位置差异
- 启动新的动画
在 React 中,这可能比看起来要难。我浪费了很多时间与之作斗争。必须缓存当前动画对象。一个好方法是创建一个Map
,以便通过 ID 获取动画。然后,我们需要获取正在移动元素的大小和位置。有两种方法可以做到
- 使用函数组件:只需在函数主体中循环遍历所有动画元素,并缓存当前位置。
- 使用类组件:使用
getSnapshotBeforeUpdate
生命周期方法。
事实上,React 官方文档建议使用 getSnapshotBeforeUpdate
,“因为在“渲染”阶段的生命周期(如 render
)和“提交”阶段的生命周期(如 getSnapshotBeforeUpdate
和 componentDidUpdate
)之间可能存在延迟。”但是,目前还没有这个方法的 Hook 等价物。我发现使用函数组件的主体已经足够了。
不要与浏览器对抗
我之前说过,避免与浏览器对抗,并尝试按照浏览器的方式做事。如果我们需要为简单的尺寸变化添加动画,那么请考虑 CSS 是否足够(例如 transform: scale()
)。我发现 FLIP 动画最适合浏览器无法真正帮助的情况
- 为 DOM 位置变化添加动画(如我们上面所做的那样)
- 共享布局动画
第二个是第一个的更复杂版本。有两个 DOM 元素表现和看起来像一个元素,并改变其位置(而另一个元素被卸载/隐藏)。这种技巧可以实现一些很酷的动画。例如,这个动画是由我构建的一个名为 react-easy-flip
的库使用这种方法实现的
库
有相当多的库使 React 中的 FLIP 动画更容易,并抽象了样板代码。当前积极维护的库包括:react-flip-toolkit
和我的库,react-easy-flip
。
如果你不介意使用更重量级的库,但它可以实现更通用的动画,查看 framer-motion
。它还可以实现很酷的共享布局动画! 这里有一段视频深入探讨了该库。
资源和参考文献
- Animating the Unanimatable,作者 Josh W. Comeau
- 构建性能卓越的展开和收起动画,作者 Paul Lewis 和 Stephen McGruer
- Magic Motion 背后的魔法,作者 Matt Perry
- 从 JavaScript 中使用动画 CSS 变量,由 @keyframers 推文
- 深入了解现代 Web 浏览器(第 3 部分),作者 Mariko Kosaka
- 在 React 中简单构建复杂的 UI 动画,作者 Alex Holachek
- 使用 FLIP 技术为布局添加动画,作者 David Khourshid
- 使用 React Hooks 实现流畅动画,再谈,作者 Kirill Vasiltsov
- 使用 React Hooks 实现共享元素过渡,作者 Jayant Bhawal
你是抄袭他还是他在抄袭你?或者你们都在抄袭其他人?
http://webitlabs.com/2020/06/16/every-thing-you-must-know-about-flip-animations-in-react/
我制作了标题图形并编辑了内容,所以我可以确认这是来源。
我发现这篇文章有近十个副本。不确定是否可以采取措施删除它们:一些副本署名了我的名字,一些副本未署名并直接窃取内容。