优雅的实现React中的动画过渡

·  阅读 2752
优雅的实现React中的动画过渡

最简单的动画组件实现

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

animate.png

codepen地址

animate.gif

实现一组动画的过渡

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

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

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

比如:

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

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

animates1.png

animates2.png

codepen地址

animates.gif

上述组件目前存在的问题

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

FLIP动画

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

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

效果1.gif

效果2.gif

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

闪烁

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

flip1.png

codepen演示

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

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

雏形

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

flip2.png

codepen演示

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

fli3.gif

完善

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

flip3.png

codepen演示

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

flip4.gif

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

我们为什么要使用之前的样式值减去当前的样式值?

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

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

100ms

There is a window of 100ms after someone interacts with your site where you’re able to do work without them noticing.

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

100ms.jpg

FLIP 与 Web Animations API

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

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

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

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

flip5.png

codepen演示

flip6.gif

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

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的雏形

flip7.png

codepen演示

flip8.gif

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

❓ 目前存在的问题

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

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

如何解决呢?

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

flip8.png

codepen演示

flip9.gif

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

🚧 Flip动画需要注意的点

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

Flip如何实现交错效果?

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

效果3.gif

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

这个问题解决的思路,我参考了 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。谢谢🙏

项目地址:github.com/peoplesing1…

参考

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改