[Js进阶]requestAnimationFrame应用
[Js进阶]requestAnimationFrame应用
简介
requestAnimationFrame
是一个用于动画效果的 API,它使用浏览器的刷新率来执行回调函数,通常每秒钟执行 60 次。与 setTimeout
和 setInterval
不同的是,requestAnimationFrame
被视为一个特殊的“宏任务”。
宏任务是一个在事件循环队列中排队等待执行的任务。与宏任务不同的是,“微任务”是一个在当前任务完成后立即执行的任务,它通常用于处理异步操作的结果。
在 JavaScript 中,异步操作通常是通过回调函数或 Promise 处理的。当异步操作完成后,回调函数将被添加到微任务队列中,以便在当前任务完成后立即执行。这意味着,微任务的执行顺序优先于下一个宏任务。
对于 requestAnimationFrame
,由于它被视为一个宏任务,因此它的执行顺序优先于微任务。这意味着,如果在 requestAnimationFrame
回调函数中添加了微任务,那么这些微任务将在下一个宏任务(即下一个 requestAnimationFrame
回调函数)执行前被执行。
应用场景
requestAnimationFrame
主要用于实现流畅的动画效果,它可以在浏览器的重绘周期内执行指定的函数,从而避免由于频繁的重绘导致的性能问题。除了动画之外,requestAnimationFrame
还可以应用于以下场景:
- 实现平滑滚动效果。使用
requestAnimationFrame
可以在滚动过程中不断更新滚动位置,并且可以控制滚动的速度和加速度,从而实现更加自然和流畅的滚动效果。 - 实现倒计时效果。使用
requestAnimationFrame
可以控制倒计时的间隔和更新频率,并且可以在倒计时结束之后立即执行指定的函数,从而实现更加精确和可控的倒计时效果。 - 实现拖拽效果。使用
requestAnimationFrame
可以在拖拽过程中不断更新元素的位置,并且可以控制拖拽的速度和加速度,从而实现更加自然和流畅的拖拽效果。 - 实现页面的渐变效果。使用
requestAnimationFrame
可以在一段时间内不断更新页面元素的样式,并且可以控制渐变的速度和方向,从而实现更加自然和流畅的渐变效果。
…
需要注意的是,requestAnimationFrame
并不是万能的,它也有一些局限性。例如,在一些需要高精度计算的场景下,requestAnimationFrame
可能无法满足需求,此时可能需要使用其他更加精细的计时器或者事件来实现。
实现平滑滚动效果
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>Smooth Scroll</title><style>body {height: 8000px;}.scroll-btn {position: fixed;bottom: 20px;right: 20px;cursor: pointer;}</style></head><body><button class="scroll-btn">Scroll to Bottom</button><script>const btn = document.querySelector('.scroll-btn');const scrollHeight = document.body.scrollHeight;const screenHeight = window.innerHeight;function scrollToBottom() {const currentScroll = window.pageYOffset;const remainingScroll = scrollHeight - currentScroll - screenHeight;if (remainingScroll > 0) {window.scrollTo(0, currentScroll + remainingScroll / 20);requestAnimationFrame(scrollToBottom);}}btn.addEventListener('click', scrollToBottom);</script></body>
</html>
在这个示例中,我们首先获取了页面的滚动高度和屏幕高度。然后,定义了一个scrollToBottom
函数,这个函数会在每一帧中更新滚动位置,并且使用requestAnimationFrame
函数注册下一次动画帧。在scrollToBottom
函数中,我们首先计算出当前滚动位置和还剩余的滚动距离,然后根据剩余滚动距离计算出滚动的距离,并使用window.scrollTo
函数实现滚动。最后,如果还有剩余的滚动距离,就使用requestAnimationFrame
函数注册下一次动画帧,从而实现平滑滚动效果。
实现倒计时效果
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>Countdown</title><style>body {height: 100vh;display: flex;justify-content: center;align-items: center;}#countdown {font-size: 48px;}</style></head><body><div id="countdown"></div><script>const countdown = document.querySelector('#countdown');let endTime = Date.now() + 10000; // 倒计时 10sfunction updateCountdown() {const remainingTime = endTime - Date.now();if (remainingTime > 0) {const remainingSeconds = Math.floor(remainingTime / 1000);countdown.innerText = remainingSeconds;requestAnimationFrame(updateCountdown);} else {countdown.innerText = '0';}}// updateCountdown()requestAnimationFrame(updateCountdown);</script></body>
</html>
在这个示例中,我们首先定义了一个endTime
变量,表示倒计时结束的时间。然后,定义了一个updateCountdown
函数,这个函数会在每一帧中更新倒计时,并且使用requestAnimationFrame
函数注册下一次动画帧。在updateCountdown
函数中,我们首先计算出当前的剩余时间,然后根据剩余时间计算出剩余秒数,并更新倒计时的显示。最后,如果还有剩余时间,就使用requestAnimationFrame
函数注册下一次动画帧,从而实现倒计时效果。
实现拖拽效果
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>Drag and Drop</title><style>#drag {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);width: 100px;height: 100px;background-color: #f00;cursor: move;}</style></head><body><div id="drag"></div><script>const drag = document.querySelector('#drag');let isDragging = false;let startX = 0;let startY = 0;function handleMouseDown(event) {isDragging = true;startX = event.clientX;startY = event.clientY;}function handleMouseMove(event) {if (isDragging) {const deltaX = event.clientX - startX;const deltaY = event.clientY - startY;const currentX = parseInt(drag.style.left) || 0;const currentY = parseInt(drag.style.top) || 0;drag.style.left = currentX + deltaX + 'px';drag.style.top = currentY + deltaY + 'px';startX = event.clientX;startY = event.clientY;requestAnimationFrame(handleMouseMove);}}function handleMouseUp() {isDragging = false;}drag.addEventListener('mousedown', handleMouseDown);document.addEventListener('mousemove', handleMouseMove);document.addEventListener('mouseup', handleMouseUp);</script></body>
</html>
在这个示例中,我们首先定义了一个可拖拽的元素drag
,并且为它添加了鼠标样式和事件。然后,定义了三个函数handleMouseDown
、handleMouseMove
和handleMouseUp
,分别处理鼠标按下、鼠标移动和鼠标抬起事件。在handleMouseDown
函数中,我们记录下鼠标按下时的位置。在handleMouseMove
函数中,我们首先判断鼠标是否按下,如果按下了,就计算出鼠标移动的距离,并根据距离更新元素的位置。最后,如果正在拖拽,就使用requestAnimationFrame
函数注册下一次动画帧,从而实现平滑拖拽效果。在handleMouseUp
函数中,我们记录下鼠标抬起的事件,表示拖拽结束。
实现渐变效果
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>渐变效果示例</title>
</head>
<body><div id="myElement" style="width: 200px; height: 200px;"></div><script>const element = document.getElementById('myElement'); // 获取需要渐变的元素let start = null; // 记录动画开始时间const duration = 10000; // 动画持续时间(单位:毫秒)const startColor = [255, 255, 255]; // 起始颜色(白色)const endColor = [0, 0, 0]; // 结束颜色(黑色)function animate(timestamp) {if (!start) start = timestamp; // 如果动画未开始,则记录开始时间const elapsed = timestamp - start; // 计算动画已经进行的时间const progress = Math.min(elapsed / duration, 1); // 计算动画进度(0-1)const color = interpolateColor(startColor, endColor, progress); // 计算当前颜色element.style.backgroundColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; // 设置元素背景颜色if (progress < 1) {requestAnimationFrame(animate); // 如果动画未结束,则继续执行动画}}requestAnimationFrame(animate); // 开始执行动画function interpolateColor(startColor, endColor, progress) {const color = [];for (let i = 0; i < 3; i++) {color[i] = Math.round(startColor[i] + (endColor[i] - startColor[i]) * progress);}return color;}</script>
</body>
</html>
Vue中结合使用
以下是一个简单的自定义指令示例,用于在元素滚动时实现类似于 Parallax 效果
<template><div class="parallax" v-parallax="{ speed: 0.2 }"><img src="image.jpg"></div>
</template><script>
export default {directives: {parallax: {bind: function (el, binding) {const speed = binding.value.speed || 0.1;let lastScrollTop = 0;let animationFrameId = null;function animate() {const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;const offset = (scrollTop - lastScrollTop) * speed;lastScrollTop = scrollTop;el.style.transform = `translateY(${offset}px)`;animationFrameId = requestAnimationFrame(animate);}animate();el.__parallax_unbind__ = function() {cancelAnimationFrame(animationFrameId);};},unbind: function (el) {el.__parallax_unbind__();delete el.__parallax_unbind__;}}}
};
</script><style scoped>
.parallax {position: relative;height: 300px;overflow: hidden;
}.parallax img {position: absolute;top: 0;left: 0;width: 100%;height: auto;
}
</style>
在这个例子中,我们创建了一个名为 v-parallax
的指令,并传递了一个 speed
参数用于控制滚动速度。在指令的 bind
钩子中,我们使用 requestAnimationFrame
来实现动画效果,根据滚动的位置计算出偏移量,并将其应用于元素的 transform
属性。
在指令的 unbind
钩子中,我们清除了动画效果,以避免在元素被销毁时仍然继续执行动画。
在模板中,我们将指令应用于包含图片的 div
元素上,并设置了一些基本的样式。当页面滚动时,图片将以不同的速度向上或向下滚动,实现类似于 Parallax 效果。
注意,在这个示例中,我们使用了 window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
来获取滚动的位置。这是因为不同的浏览器可能会使用不同的属性来表示滚动偏移量。
另外,由于 requestAnimationFrame
在每秒钟执行大约 60 次,因此它比使用 setTimeout
或 setInterval
更加高效和流畅。
React中结合使用
实现一个逐渐变暗的背景效果
import React, { useRef, useEffect } from 'react';function Background() {const backgroundRef = useRef(null);useEffect(() => {let requestId;let opacity = 1;function animate() {opacity -= 0.01;if (opacity < 0) {opacity = 0;}backgroundRef.current.style.opacity = opacity;if (opacity > 0) {requestId = requestAnimationFrame(animate);}}requestId = requestAnimationFrame(animate);return () => {cancelAnimationFrame(requestId);};}, []);return (<divref={backgroundRef}style={{position: 'fixed',top: 0,left: 0,width: '100%',height: '100%',backgroundColor: '#000',opacity: 1}}/>);
}
在这个例子中,我们使用 useRef
钩子创建了一个名为 backgroundRef
的引用,并将其绑定到一个 div
元素上。在 useEffect
钩子中,我们使用 requestAnimationFrame
实现了逐渐变暗的背景效果。
在 useEffect
钩子中,我们使用了一个 requestId
变量来存储 requestAnimationFrame
的返回值,以便在组件被卸载时可以清除它。我们还使用了一个 opacity
变量来存储背景的透明度,并在每次 animate
函数调用中减少它。当透明度小于 0 时,我们将其设置为 0,并停止调用 requestAnimationFrame
。
在组件的返回值中,我们将 backgroundRef
绑定到一个 div
元素上,并设置了一些基本的样式。在 useEffect
钩子中,我们使用 backgroundRef 的
current属性来获取该元素,并将其透明度设置为
opacity` 变量的值。在初次渲染时,背景的透明度将被设置为 1。
当组件被卸载时,我们使用 cancelAnimationFrame
函数清除 requestAnimationFrame
的调用。这是为了防止在组件卸载后仍然继续执行动画。
与setTimeout比较
requestAnimationFrame
和 setTimeout
都是用于异步执行代码的 JavaScript API。
异同点:
- 执行时机:
setTimeout
函数会在指定的时间间隔后执行回调函数,而requestAnimationFrame
则会在浏览器下一次重绘之前执行回调函数。通常情况下,浏览器的重绘频率是每秒钟 60 次,因此requestAnimationFrame
通常比setTimeout
更加高效、流畅。 - 回调函数参数:
setTimeout
函数的回调函数会接收一个可选的参数,表示在指定的时间间隔后是否重复执行回调函数。而requestAnimationFrame
的回调函数不接受任何参数。 - 取消执行:通过调用
clearTimeout
函数可以取消setTimeout
的执行,而通过调用cancelAnimationFrame
函数可以取消requestAnimationFrame
的执行。 - 兼容性:
setTimeout
是一个比较老的 API,在所有主流浏览器中都得到了支持。而requestAnimationFrame
则是比较新的 API,在一些旧的浏览器中可能不被支持。
异同点总结:
相同点:都是用于异步执行代码的 JavaScript API。
不同点:
-
执行时机不同,
setTimeout
在指定时间后执行回调函数,requestAnimationFrame
在浏览器下一次重绘前执行回调函数。 -
回调函数参数不同,
setTimeout
的回调函数接受一个可选的参数,表示是否重复执行回调函数,requestAnimationFrame
不接受任何参数。 -
取消执行方式不同,
clearTimeout
取消setTimeout
的执行,cancelAnimationFrame
取消requestAnimationFrame
的执行。 -
兼容性不同,
setTimeout
在所有主流浏览器中都得到了支持,requestAnimationFrame
在一些旧的浏览器中可能不被支持。另外,需要注意的是,
setTimeout
和requestAnimationFrame
适用于不同的场景。setTimeout
通常用于延迟执行一段代码,例如实现一个倒计时功能。而requestAnimationFrame
则通常用于实现动画效果,因为它能够根据浏览器的刷新率自动调整动画的帧率,从而使得动画更加流畅。
与setInterval比较
异同点:
- 执行时机:
setInterval
函数会在指定的时间间隔后重复执行回调函数,而requestAnimationFrame
则会在浏览器下一次重绘之前执行回调函数。与setTimeout
一样,requestAnimationFrame
通常比setInterval
更加高效、流畅。 - 回调函数参数:
setInterval
函数的回调函数会接收一个可选的参数,表示在指定的时间间隔后是否重复执行回调函数。而requestAnimationFrame
的回调函数不接受任何参数。 - 取消执行:通过调用
clearInterval
函数可以取消setInterval
的执行,而通过调用cancelAnimationFrame
函数可以取消requestAnimationFrame
的执行。 - 执行间隔:
setInterval
函数的执行间隔是固定的,而requestAnimationFrame
的执行间隔则会根据浏览器的刷新率自动调整。因此,requestAnimationFrame
更加适合实现动画效果,而setInterval
更适合实现一些周期性的操作,例如定时发送心跳包等。
异同点总结:
相同点:都是用于周期性地执行代码的 JavaScript API。
不同点:
-
执行时机不同,
setInterval
在指定时间间隔后重复执行回调函数,requestAnimationFrame
在浏览器下一次重绘前执行回调函数。 -
回调函数参数不同,
setInterval
的回调函数接受一个可选的参数,表示是否重复执行回调函数,requestAnimationFrame
不接受任何参数。 -
取消执行方式不同,
clearInterval
取消setInterval
的执行,cancelAnimationFrame
取消requestAnimationFrame
的执行。 -
执行间隔不同,
setInterval
的执行间隔是固定的,而requestAnimationFrame
的执行间隔则会根据浏览器的刷新率自动调整。另外,需要注意的是,
setInterval
和requestAnimationFrame
适用于不同的场景。setInterval
通常用于定时执行一些简单的周期性操作,例如每隔一定时间打印一次日志。而requestAnimationFrame
则通常用于实现动画效果,因为它能够根据浏览器的刷新率自动调整动画的帧率,从而使得动画更加流畅。