> 文章列表 > [Js进阶]requestAnimationFrame应用

[Js进阶]requestAnimationFrame应用

[Js进阶]requestAnimationFrame应用

[Js进阶]requestAnimationFrame应用

简介

requestAnimationFrame 是一个用于动画效果的 API,它使用浏览器的刷新率来执行回调函数,通常每秒钟执行 60 次。与 setTimeoutsetInterval 不同的是,requestAnimationFrame 被视为一个特殊的“宏任务”。

宏任务是一个在事件循环队列中排队等待执行的任务。与宏任务不同的是,“微任务”是一个在当前任务完成后立即执行的任务,它通常用于处理异步操作的结果。

在 JavaScript 中,异步操作通常是通过回调函数或 Promise 处理的。当异步操作完成后,回调函数将被添加到微任务队列中,以便在当前任务完成后立即执行。这意味着,微任务的执行顺序优先于下一个宏任务。

对于 requestAnimationFrame,由于它被视为一个宏任务,因此它的执行顺序优先于微任务。这意味着,如果在 requestAnimationFrame 回调函数中添加了微任务,那么这些微任务将在下一个宏任务(即下一个 requestAnimationFrame 回调函数)执行前被执行。

应用场景

requestAnimationFrame主要用于实现流畅的动画效果,它可以在浏览器的重绘周期内执行指定的函数,从而避免由于频繁的重绘导致的性能问题。除了动画之外,requestAnimationFrame还可以应用于以下场景:

  1. 实现平滑滚动效果。使用requestAnimationFrame可以在滚动过程中不断更新滚动位置,并且可以控制滚动的速度和加速度,从而实现更加自然和流畅的滚动效果。
  2. 实现倒计时效果。使用requestAnimationFrame可以控制倒计时的间隔和更新频率,并且可以在倒计时结束之后立即执行指定的函数,从而实现更加精确和可控的倒计时效果。
  3. 实现拖拽效果。使用requestAnimationFrame可以在拖拽过程中不断更新元素的位置,并且可以控制拖拽的速度和加速度,从而实现更加自然和流畅的拖拽效果。
  4. 实现页面的渐变效果。使用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,并且为它添加了鼠标样式和事件。然后,定义了三个函数handleMouseDownhandleMouseMovehandleMouseUp,分别处理鼠标按下、鼠标移动和鼠标抬起事件。在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 次,因此它比使用 setTimeoutsetInterval 更加高效和流畅。

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比较

requestAnimationFramesetTimeout 都是用于异步执行代码的 JavaScript API。

异同点:

  1. 执行时机:setTimeout 函数会在指定的时间间隔后执行回调函数,而 requestAnimationFrame 则会在浏览器下一次重绘之前执行回调函数。通常情况下,浏览器的重绘频率是每秒钟 60 次,因此 requestAnimationFrame 通常比 setTimeout 更加高效、流畅。
  2. 回调函数参数:setTimeout 函数的回调函数会接收一个可选的参数,表示在指定的时间间隔后是否重复执行回调函数。而 requestAnimationFrame 的回调函数不接受任何参数。
  3. 取消执行:通过调用 clearTimeout 函数可以取消 setTimeout 的执行,而通过调用 cancelAnimationFrame 函数可以取消 requestAnimationFrame 的执行。
  4. 兼容性:setTimeout 是一个比较老的 API,在所有主流浏览器中都得到了支持。而 requestAnimationFrame 则是比较新的 API,在一些旧的浏览器中可能不被支持。

异同点总结:

相同点:都是用于异步执行代码的 JavaScript API。

不同点:

  1. 执行时机不同,setTimeout 在指定时间后执行回调函数,requestAnimationFrame 在浏览器下一次重绘前执行回调函数。

  2. 回调函数参数不同,setTimeout 的回调函数接受一个可选的参数,表示是否重复执行回调函数,requestAnimationFrame 不接受任何参数。

  3. 取消执行方式不同,clearTimeout 取消 setTimeout 的执行,cancelAnimationFrame 取消 requestAnimationFrame 的执行。

  4. 兼容性不同,setTimeout 在所有主流浏览器中都得到了支持,requestAnimationFrame 在一些旧的浏览器中可能不被支持。

    另外,需要注意的是,setTimeoutrequestAnimationFrame 适用于不同的场景。setTimeout 通常用于延迟执行一段代码,例如实现一个倒计时功能。而 requestAnimationFrame 则通常用于实现动画效果,因为它能够根据浏览器的刷新率自动调整动画的帧率,从而使得动画更加流畅。

与setInterval比较

异同点:

  1. 执行时机:setInterval 函数会在指定的时间间隔后重复执行回调函数,而 requestAnimationFrame 则会在浏览器下一次重绘之前执行回调函数。与 setTimeout 一样,requestAnimationFrame 通常比 setInterval 更加高效、流畅。
  2. 回调函数参数:setInterval 函数的回调函数会接收一个可选的参数,表示在指定的时间间隔后是否重复执行回调函数。而 requestAnimationFrame 的回调函数不接受任何参数。
  3. 取消执行:通过调用 clearInterval 函数可以取消 setInterval 的执行,而通过调用 cancelAnimationFrame 函数可以取消 requestAnimationFrame 的执行。
  4. 执行间隔:setInterval 函数的执行间隔是固定的,而 requestAnimationFrame 的执行间隔则会根据浏览器的刷新率自动调整。因此,requestAnimationFrame 更加适合实现动画效果,而 setInterval 更适合实现一些周期性的操作,例如定时发送心跳包等。

异同点总结:

相同点:都是用于周期性地执行代码的 JavaScript API。

不同点:

  1. 执行时机不同,setInterval 在指定时间间隔后重复执行回调函数,requestAnimationFrame 在浏览器下一次重绘前执行回调函数。

  2. 回调函数参数不同,setInterval 的回调函数接受一个可选的参数,表示是否重复执行回调函数,requestAnimationFrame 不接受任何参数。

  3. 取消执行方式不同,clearInterval 取消 setInterval 的执行,cancelAnimationFrame 取消 requestAnimationFrame 的执行。

  4. 执行间隔不同,setInterval 的执行间隔是固定的,而 requestAnimationFrame 的执行间隔则会根据浏览器的刷新率自动调整。

    另外,需要注意的是,setIntervalrequestAnimationFrame 适用于不同的场景。setInterval 通常用于定时执行一些简单的周期性操作,例如每隔一定时间打印一次日志。而 requestAnimationFrame 则通常用于实现动画效果,因为它能够根据浏览器的刷新率自动调整动画的帧率,从而使得动画更加流畅。