前言
有时候我们要做一个组件的动画效果,想到的就是用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的使用