「React」如何在React中优雅的实现动画

最简单的动画组件实现

动画的本质,无非就是一个状态样式到另一个状态样式的过渡。最简单的动画组件,我们只需要指定两个状态的样式(进入的样式,离开的样式),以及一个开关(控制状态),即可完成。

「React」如何在React中优雅的实现动画

codepen地址

「React」如何在React中优雅的实现动画

实现一组动画的过渡

实现一组动画的过渡。我们只需要在多个最简单的动画组件的基础之上,设置一个统一的开关,统一控制,多个动画组件动画的状态即可。如果想实现有交错的过渡(有时间间隔的过渡),我们只需要根据动画组件在一组元素中的索引位置,设置合适的延迟即可。

为了引入统一的开关的控制,我们为动画组件添加一个父级组件,父级组件的开关控制所有子组件的开关状态。父组件使用React.Context将自己开关的状态,下发给子组件。

为了实现交错效果,我们需要为列表中子组件设置不同长度的延迟。延迟时长和子组件在列表索引,以及开关的状态有关。

比如:

在开关设置为true, 需要显示入场的动画。延迟自上而下,依次增大(0ms, 100ms, 200ms, 300ms)

在开关设置为false, 需要显示出场的动画。延迟自上而下,依次减小(300ms, 200ms, 100ms, 0ms)

「React」如何在React中优雅的实现动画

「React」如何在React中优雅的实现动画

codepen地址

「React」如何在React中优雅的实现动画

上述组件目前存在的问题

  1. 节点必须事先已经渲染好,对于动态插入的节点,这些动画组件无能为力。(在下方我们参考了`react-transition-group实现解决这个问题)
  2. 如果元素起始样式有display: none动画将不会起效果(这个问题其实和动态插入节点属于一类问题)。
  3. 对于一组列表节点。新的节点的插入,和删除时。其他节点的过渡很生硬,没有动画效果(我们可以使用flip动画解决这个问题)。

FLIP动画

FLIP动画实现原理是: 缓存元素起点的位置, 然后将元素置于终点的位置,计算终点与起点的差值,根据差值应用动画。

我们先看看flip动画强大的效果

「React」如何在React中优雅的实现动画

「React」如何在React中优雅的实现动画

接下来,我们来一步一步实现一个简易的flip动画,然后再尝试在react中实现。

闪烁

请问下面的代码,会造成闪烁的问题吗?

「React」如何在React中优雅的实现动画

codepen演示

答案: 是不会。具体原因和浏览器的事件队列有关。点击事件的代码,我们必须执行完成当前的任务(当前代码段的执行)才会进行浏览器渲染。

这一点对我们很重要。再重申一遍flip动画的原理,缓存元素起点的位置, 然后将元素置于终点的位置,计算终点与起点的差值,根据差值应用动画。

雏形

然后基于上面的代码,我们目前可以实现一个简易的flip动画

「React」如何在React中优雅的实现动画

codepen演示

我们可以看到,动画已经实现,但是目前动画的计算还是固定的,我们接下来尝试让它自动化。

「React」如何在React中优雅的实现动画

完善

我们尝试对, 之前状态和当前状态的属性,做自动的差值计算。

「React」如何在React中优雅的实现动画

codepen演示

我们目前已经实现了,宽度和x轴的flip动画。

「React」如何在React中优雅的实现动画

🤔️为什么要这样计算(之前的位置 - 现在的位置)?

FLIP动画的原理是基于当前位置和起始位置的动画,我们在做动画的时候,元素其实已经到达了结束的位置。

比如当前的位置是100px, 开始位置是0px。flip动画需要模拟从0px到100px的过程,但是当前位置已经是100px了,所以我们必须使用 translateX(0 - 100px), 模拟动画开始时的0px的位置。

100ms

使用flip动画时,切记计算不能超过100ms,如果超过100ms用户会感到卡顿。

「React」如何在React中优雅的实现动画

FLIP 与 Web Animations API

目前距离实现一个真正的flip动画库还有不少的距离。继续使用 requestAnimationFrame 会很困难,太复杂了。

既然flip动画,是基于结束位置和开始位置的动画,那么有没有什么好办法,不需要我们手动的去调整。只需要提供初始位置和结束位置完成动画呢?我们可以使用Web Animations API。

对于 Web Animations API本身,我在这里不想做过多的介绍。大家只需要知道,使用Web Animations API后,我们只需要设置开始的样式,和结束的样式,动画就会自动完成。

我们将上面的demo,改造成使用Web Animations API的形式。

「React」如何在React中优雅的实现动画

codepen演示

「React」如何在React中优雅的实现动画

可以看到,我们在代码里只需要设置开始和结束的样式,动画就会自动过渡完成。

React 与 FLIP

如何在react中完成flip动画呢?我们首先回忆下在js中flip动画的逻辑

  1. 缓存元素起始位置
  2. 将元素移动到结束的位置
  3. 获取当前的位置,并计算当前的位置与缓存的起始位置的差值。
  4. 下一帧开始时,开始做动画

我们可以发现,第1,2,3步都是发生在渲染到屏幕之前(或者说渲染到屏幕的那一刻)。那么在react中,有什么hook发生在渲染到页面的那一刻呢?答案是: 函数组件中是useLayoutEffect。class组件中是componentDidUpdate

我们整理下在react中flip动画的实现逻辑

  1. 在页面第一次useEffect, 元素渲染完成。这时同时缓存元素的位置。
  2. state发生变化,组件需要重新渲染
  3. 在组件重新渲染到屏幕那一刻,在useLayoutEffect中,我们获取最新的位置。并计算当前的位置与缓存的起始位置的差值。
  4. 动画开始执行

那么接下来我们来实现一个react中flip的雏形

「React」如何在React中优雅的实现动画

codepen演示

「React」如何在React中优雅的实现动画

bigo!我们成功在react中实现了flip动画

❓ 目前存在的问题

如果我们在flip动画运行过程中,切换动画。动画会出现闪烁,我们现在来着手解决这个问题。

我们先来思考一下这个问题产生的原因。动画在运行过程中,还没有到达终点,这时切换动画,动画元素会被强行移动到终点的位置,然后进行下一次动画,这就是动画闪烁的原因。

如何解决呢?

  1. 在切换动画的时候,如果上一次动画没有结束,我们手动将其结束
  2. 在切换动画的时候,更新位置的缓存。

「React」如何在React中优雅的实现动画

codepen演示

「React」如何在React中优雅的实现动画

虽然目前已经实现flip动画的效果,但是距离封装成可用的库还有些距离,如果大家想要了解的更多,可以查看我封装好的源码(原理和上面的文章是一模一样的)。仓库地址: https://github.com/peoplesing...

🚧 Flip动画需要注意的点

  1. flip计算动画位置时,元素上最好不要有transition的css属性,会影响到位置的计算。
  2. 之前的计算缓存位置时,都是相对于body的位置。但是如果存在有滚动条时,缓存的位置会有问题。解决办法是,基于动画元素的父级元素计算位置,而不是body的位置。

Flip如何实现交错效果?

好吧。目前我的库中,交错效果的完善解决方案还没有实现。但是主体思路是有了,并有了简易的实现版本 见下方👇

「React」如何在React中优雅的实现动画

🔍🔍🔍 动态插入节点的动画处理

这个问题解决的思路,我参考了 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 库的处理方式:

  1. 插入的节点,先渲染dom,然后再做动画
  2. 删除的节点,先做动画,然后再删除dom

写在最后

如果您对我的文章感到满意,还请麻烦您给我的文章点一个赞。如果您喜欢我的小项目,还请帮我的小项目点一个star。谢谢🙏

项目地址:https://github.com/peoplesing...

参考

  • react-transition-group源码
  • FLIP Your Animations

以上是 「React」如何在React中优雅的实现动画 的全部内容, 来源链接: utcz.com/a/56913.html

回到顶部