一步步教你在React中使用requestAnimationFrame实现动画,提升性能!


前言

有时候我们要做一个组件的动画效果,想到的就是用css来控制,但是会有一些缺点,只能实现一些简单的动画效果,无法实现更复杂的动画效果;动画帧无法控制。如果想实现复杂和较精确的动画效果,就需要用requestAnimationFrame,它可以实现更好的性能和更好的灵活动画控制。而且可根据浏览器刷新率来决定每帧的执行时间,保证了动画的流畅性。
接下来我们使用requestAnimationFrame来实现一个动画效果,元素透明度从0到1显示后,清除该元素,前面有讲过,这里做个详细分析,教你一步步学会使用。


动画案例步骤

1. 定义一个动画帧

const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
const cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame;

2. 开始编写一个控制透明度的动画

(1)我们在useEffect中模拟页面加载完成后,通过设定的时间延迟显示动画,实现动画效果,首先定义一些变量

// 定义ANIMATION_DISAPPEAR_TIME,表示动画消失时间
const ANIMATION_DISAPPEAR_TIME = 2500;
// 定义DEFAULT_TOAST_DURATION,表示默认的toast消息显示时间
const DEFAULT_TOAST_DURATION = 2000;
// 定义MAX_FRAME,表示最大帧数,这里随便设置一秒60帧
const MAX_FRAME: number = (ANIMATION_DISAPPEAR_TIME / 1000) * 60;

useEffect(()=>{
 // 定义初始化透明度opacity为0
 const [opacity, setOpacity] = useState(0);
 // 定义一个计数器count为0
 let count = 0;
 // 定义animationId,用于后期清除动画
 let animationId: number = 0;
})

(2)好了,结合上面定义的动画帧,接下来定义updateOpacity函数,用于更新透明度

const updateOpacity = () => {
  count++;
  setOpacity(1 - count / MAX_FRAME);
  // 如果计数小于最大帧数,继续请求动画帧
  if (count < MAX_FRAME) {
    animationId = requestAnimationFrame(updateOpacity);
  } else {
    // 否则,相当于执行完一轮动画,这时候调用组件隐藏的方法
    componentLeave();
  }
};

(3)根据设置的动画显示时间,实现动画效果

const timer = setTimeout(() => {
  updateOpacity();
}, DEFAULT_TOAST_DURATION);

(4)在页面卸载时,记得清除定时器等,否则会造成内存泄漏

useEffect(()=>{
  // 处理业务...
  return () => {
    // 清除定时器
    clearTimeout(timer);
    // 清除动画帧
    cancelAnimationFrame(animationId);
  }
 })

(5)最后,我们来将代码进行组合,就可以实现可控制帧数的动画了

// 定义一个动画帧
const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame;
// 定义一个取消动画帧的函数
const cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame;
// 定义ANIMATION_DISAPPEAR_TIME,表示动画消失时间
const ANIMATION_DISAPPEAR_TIME = 2500;
// 定义DEFAULT_TOAST_DURATION,表示默认的toast消息显示时间
const DEFAULT_TOAST_DURATION = 2000;
// 定义MAX_FRAME,表示最大帧数,这里随便设置一秒60帧
const MAX_FRAME: number = (ANIMATION_DISAPPEAR_TIME / 1000) * 60;
useEffect(()=>{
  // 定义初始化透明度opacity为0
  const [opacity, setOpacity] = useState(0);
  // 定义一个计数器count为0
  let count = 0;
  // 定义animationId,用于后期清除动画
  let animationId: number = 0;
  const updateOpacity = () => {
    count++;
    setOpacity(1 - count / MAX_FRAME);
    // 如果计数小于最大帧数,继续请求动画帧
    if (count < MAX_FRAME) {
      animationId = requestAnimationFrame(updateOpacity);
    } else {
      // 否则,相当于执行完一轮动画,这时候调用组件隐藏的方法
      componentLeave();
    }
  };
  return () => {
    // 清除定时器
    clearTimeout(timer);
    // 清除动画帧
    cancelAnimationFrame(animationId);
  }
 })

3. 进一步抽离,封装hook简化写法

(1)定义一个Hook函数,用于在组件卸载时清除定时器

import { useEffect, useRef } from 'react'
// 参数:
// - callback:定时器触发时执行的回调函数
// - clearCallback:定时器卸载时执行的清除回调函数
// - delay:定时器延迟执行的时间
export const useTimeoutWithUnmount = (callback: () => void, clearCallback: () => void, delay: number) => {
  // 使用useRef保存回调函数和清除回调函数的引用
  const savedCallback = useRef(() => { })
  const savedClearCallback = useRef(() => { })
  // 在组件挂载时注册回调函数和清除回调函数
  useEffect(() => {
    savedCallback.current = callback
    savedClearCallback.current = clearCallback
  })
  // 在组件挂载和卸载时设置定时器
  useEffect(() => {
    // 定义定时器的回调函数
    const tick = () => {
      // 执行保存的回调函数
      savedCallback.current()
    }
    // 设置定时器,执行tick函数
    const listener = setTimeout(tick, delay)
    // 页面卸载时,返回一个清除定时器的函数
    return () => {
      // 清除定时器
      clearTimeout(listener)
      // 执行清除回调函数
      savedClearCallback.current()
    }
  }, [delay])
}

(2)将useEffect换成useTimeoutWithUnmount,传入执行函数、以及页面销毁时的需要清除的动作

const [opacity, setOpacity] = useState(0);
let animationId: number = 0;
useTimeoutWithUnmount(
  () => {
    // 定义一个计数器count为0
    let count = 0;
    // 定义animationId,用于后期清除动画
    let animationId: number = 0;
    const updateOpacity = () => {
      count++;
      setOpacity(1 - count / MAX_FRAME);
      // 如果计数小于最大帧数,继续请求动画帧
      if (count < MAX_FRAME) {
        animationId = requestAnimationFrame(updateOpacity);
      } else {
        // 否则,相当于执行完一轮动画,这时候调用组件隐藏的方法
        componentLeave();
      }
   },
  () => {
    animationId && cancelAnimationFrame(animationId);
  },
  DEFAULT_TOAST_DURATION
);

总结

好了,到这里整个使用方式就大概讲完了,如果想看下具体实例,可以看之前文章全局常用组件Toast封装,以及rxjs和useReducer的使用

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值