「React」如何在React中优雅的实现动画
最简单的动画组件实现
动画的本质,无非就是一个状态样式到另一个状态样式的过渡。最简单的动画组件,我们只需要指定两个状态的样式(进入的样式,离开的样式),以及一个开关(控制状态),即可完成。
codepen地址
实现一组动画的过渡
实现一组动画的过渡。我们只需要在多个最简单的动画组件的基础之上,设置一个统一的开关,统一控制,多个动画组件动画的状态即可。如果想实现有交错的过渡(有时间间隔的过渡),我们只需要根据动画组件在一组元素中的索引位置,设置合适的延迟即可。
为了引入统一的开关的控制,我们为动画组件添加一个父级组件,父级组件的开关控制所有子组件的开关状态。父组件使用React.Context将自己开关的状态,下发给子组件。
为了实现交错效果,我们需要为列表中子组件设置不同长度的延迟。延迟时长和子组件在列表索引,以及开关的状态有关。
比如:
在开关设置为true, 需要显示入场的动画。延迟自上而下,依次增大(0ms, 100ms, 200ms, 300ms)
在开关设置为false, 需要显示出场的动画。延迟自上而下,依次减小(300ms, 200ms, 100ms, 0ms)
codepen地址
上述组件目前存在的问题
- 节点必须事先已经渲染好,对于动态插入的节点,这些动画组件无能为力。(在下方我们参考了
`react-transition-group
实现解决这个问题) - 如果元素起始样式有
display: none
动画将不会起效果(这个问题其实和动态插入节点属于一类问题)。 - 对于一组列表节点。新的节点的插入,和删除时。其他节点的过渡很生硬,没有动画效果(我们可以使用flip动画解决这个问题)。
FLIP动画
FLIP动画实现原理是: 缓存元素起点的位置, 然后将元素置于终点的位置,计算终点与起点的差值,根据差值应用动画。
我们先看看flip动画强大的效果
接下来,我们来一步一步实现一个简易的flip动画,然后再尝试在react中实现。
闪烁
请问下面的代码,会造成闪烁的问题吗?
codepen演示
答案: 是不会。具体原因和浏览器的事件队列有关。点击事件的代码,我们必须执行完成当前的任务(当前代码段的执行)才会进行浏览器渲染。
这一点对我们很重要。再重申一遍flip动画的原理,缓存元素起点的位置, 然后将元素置于终点的位置,计算终点与起点的差值,根据差值应用动画。
雏形
然后基于上面的代码,我们目前可以实现一个简易的flip动画
codepen演示
我们可以看到,动画已经实现,但是目前动画的计算还是固定的,我们接下来尝试让它自动化。
完善
我们尝试对, 之前状态和当前状态的属性,做自动的差值计算。
codepen演示
我们目前已经实现了,宽度和x轴的flip动画。
🤔️为什么要这样计算(之前的位置 - 现在的位置)?
FLIP动画的原理是基于当前位置和起始位置的动画,我们在做动画的时候,元素其实已经到达了结束的位置。
比如当前的位置是100px, 开始位置是0px。flip动画需要模拟从0px到100px的过程,但是当前位置已经是100px了,所以我们必须使用 translateX(0 - 100px)
, 模拟动画开始时的0px的位置。
100ms
使用flip动画时,切记计算不能超过100ms,如果超过100ms用户会感到卡顿。
FLIP 与 Web Animations API
目前距离实现一个真正的flip动画库还有不少的距离。继续使用 requestAnimationFrame 会很困难,太复杂了。
既然flip动画,是基于结束位置和开始位置的动画,那么有没有什么好办法,不需要我们手动的去调整。只需要提供初始位置和结束位置完成动画呢?我们可以使用Web Animations API。
对于 Web Animations API本身,我在这里不想做过多的介绍。大家只需要知道,使用Web Animations API后,我们只需要设置开始的样式,和结束的样式,动画就会自动完成。
我们将上面的demo,改造成使用Web Animations API的形式。
codepen演示
可以看到,我们在代码里只需要设置开始和结束的样式,动画就会自动过渡完成。
React 与 FLIP
如何在react中完成flip动画呢?我们首先回忆下在js中flip动画的逻辑
- 缓存元素起始位置
- 将元素移动到结束的位置
- 获取当前的位置,并计算当前的位置与缓存的起始位置的差值。
- 下一帧开始时,开始做动画
我们可以发现,第1,2,3步都是发生在渲染到屏幕之前(或者说渲染到屏幕的那一刻)。那么在react中,有什么hook发生在渲染到页面的那一刻呢?答案是: 函数组件中是useLayoutEffect
。class组件中是componentDidUpdate
我们整理下在react中flip动画的实现逻辑
- 在页面第一次useEffect, 元素渲染完成。这时同时缓存元素的位置。
- state发生变化,组件需要重新渲染
- 在组件重新渲染到屏幕那一刻,在
useLayoutEffect
中,我们获取最新的位置。并计算当前的位置与缓存的起始位置的差值。 - 动画开始执行
那么接下来我们来实现一个react中flip的雏形
codepen演示
bigo!我们成功在react中实现了flip动画
❓ 目前存在的问题
如果我们在flip动画运行过程中,切换动画。动画会出现闪烁,我们现在来着手解决这个问题。
我们先来思考一下这个问题产生的原因。动画在运行过程中,还没有到达终点,这时切换动画,动画元素会被强行移动到终点的位置,然后进行下一次动画,这就是动画闪烁的原因。
如何解决呢?
- 在切换动画的时候,如果上一次动画没有结束,我们手动将其结束
- 在切换动画的时候,更新位置的缓存。
codepen演示
虽然目前已经实现flip动画的效果,但是距离封装成可用的库还有些距离,如果大家想要了解的更多,可以查看我封装好的源码(原理和上面的文章是一模一样的)。仓库地址: https://github.com/peoplesing...
🚧 Flip动画需要注意的点
- flip计算动画位置时,元素上最好不要有transition的css属性,会影响到位置的计算。
- 之前的计算缓存位置时,都是相对于body的位置。但是如果存在有滚动条时,缓存的位置会有问题。解决办法是,基于动画元素的父级元素计算位置,而不是body的位置。
Flip如何实现交错效果?
好吧。目前我的库中,交错效果的完善解决方案还没有实现。但是主体思路是有了,并有了简易的实现版本 见下方👇
🔍🔍🔍 动态插入节点的动画处理
这个问题解决的思路,我参考了 react-transition-group
库 的源码。在这里我说一下,react-transition-group
库 实现的思路。
<react-transition-group>
{
list && list.map((item) => (
<react-transition>
{ item }
</react-transition>
))
}
<react-transition-group>
最外层的 <react-transition-group>
组件 并不会直接对嵌入的children进行直接渲染。而是将props.children保存为,组件的内部状态state。这样我们可以在children渲染之前,对state做一些额外的操作。
<react-transition-group>
会对于动态插入的节点,不会直接渲染。而是先将,新插入节点外层的<react-transition>
组件的动画状态设置为'Leave'态(这里处理的目的是,即使dom渲染完成后,元素也是隐藏的状态)。然后在<react-transition>
中,会先等待dom渲染完成,然后再将动画的状态设置为'Entering',完成'Leave'态到'Entering'态的动画过渡。
<react-transition-group>
会对于动态删除的节点,不会直接删除。而是先将需要删除节点外层的<react-transition>
组件的动画开关设置为false,动画开始向'Leave'态过渡。动画过渡完成后,然后会触发<react-transition>
组件的 onLeave
事件。在 onLeave
事件中会删除dom节点。
总结一下 react-transition-group
库的处理方式:
- 插入的节点,先渲染dom,然后再做动画
- 删除的节点,先做动画,然后再删除dom
写在最后
如果您对我的文章感到满意,还请麻烦您给我的文章点一个赞。如果您喜欢我的小项目,还请帮我的小项目点一个star。谢谢🙏
项目地址:https://github.com/peoplesing...
参考
- react-transition-group源码
- FLIP Your Animations
以上是 「React」如何在React中优雅的实现动画 的全部内容, 来源链接: utcz.com/a/56913.html