很多人会局限于把requestAnimationFrame应用于一些纯动画相关的需求上,但其实在前端很多业务场景下requestAnimationFrame都能用于性能优化,下面将细说一下requestAnimationFrame的具体用法和几种应用场景。
requestAnimationFrame作用与用法
requestAnimationFrame简述

基本示例
1 2 3 4 5 6
| <script lang='ts' setup> function init() { console.log('您好,我是requestAnimationFrame'); } requestAnimationFrame(init) </script>
|
.png)
但是例子上面是最基本的调用方式,并且只简单执行了一次,而对于动画是要一直执行的。
如下图所示,官方的文档对这个有说明,具体用法应该要递归调用。
递归调用示例
1 2 3 4 5 6 7
| <script lang='ts' setup> function init() { console.log('您好,递归调用requestAnimationFrame'); requestAnimationFrame(init) } requestAnimationFrame(init) </script>
|
requestAnimationFrame会一直执行,并且调用的频率通常与当前显示器的刷新率相匹配(这也是这个API核心优势),例如屏幕75hz就1秒执行75次,而且如果使用的是定时器是无法适应各种屏幕帧率的。.png)
回调函数
requestAnimationFrame执行后的回调函数有且只会返回一个参数,并且返回的参数是一个毫秒数,表示上一帧渲染的结束时间,直接看看下面示例。
1 2 3 4 5 6 7
| <script lang='ts' setup> function init(val) { console.log('您好,requestAnimationFrame回调:', val); requestAnimationFrame(init); } requestAnimationFrame(init); </script>
|
.png)
注意: 如果我们同时调用多个requestAnimationFrame,那么他们会收到相同的时间戳。
终止执行
1
| ancelAnimationFrame(requestID)
|
用法类似定时器的clearTimeout(),直接把 requestAnimationFrame 返回值传给 cancelAnimationFrame() 即可终止执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <div> <button @click='stop'>停止</button> </div> </template> <script lang='ts' setup> let myReq; function init(val) { console.log('您好,requestAnimationFrame回调:', val); myReq = requestAnimationFrame(init); } requestAnimationFrame(init);
function stop() { cancelAnimationFrame(myReq); } </script>
|
requestAnimationFrame优势
1、动画更丝滑,不会出现卡顿
对比传统的setTimeout 和 setInterval会更流畅丝滑。
由于requestAnimationFrame始终运行在js的主线程当中,因此做到了与浏览器绘制频率绝对一致。所以帧率会相当平稳,例如显示屏60hz,那么会固定1000/60ms刷新一次。
但如果使用的是setTimeout 和 setInterval,它们会受到事件队列宏任务、微任务影响会导致执行的优先级顺序有所差异,自然做不到与绘制同频。所以使用setTimeout 和 setInterval不但无法自动匹配显示屏帧率,也无法做到完全固定的时间去刷新。
2、性能更好,切后台会暂停
当我们切换页面后台运行时,requestAnimationFrame会暂停执行从而提高性能。
效果如下动图,隐藏后停止运行,切换回来接着运行。
.png)
3.应用场景:常规动画
用一个很简单的示例:用requestAnimationFrame使一张图片动态也丝滑旋转,直接看示例代码和效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| <template> <div class='container'> <div :style='imgStyle' class='earth'></div> </div> </template>
<script setup> import { ref, onMounted, reactive, onUnmounted } from 'vue';
const imgStyle = reactive({ transform: 'rotate(0deg)', });
let rafId = null;
function animate(time) { const angle = (time % 10000) / 5; imgStyle.transform = `rotate(${angle}deg)`;
rafId = window.requestAnimationFrame(animate); }
onMounted(() => { rafId = window.requestAnimationFrame(animate); });
onUnmounted(() => { if (rafId) { window.cancelAnimationFrame(rafId); } }); </script>
<style scoped> body { box-sizing: border-box; background-color: #ccc; height: 100vh; display: flex; justify-content: center; align-items: center; }
.container { position: relative; height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.earth { height: 100px; width: 100px; background-size: cover; border-radius: 50%; background-image: url('@/assets/images/about_advantage_3.png'); } </style>
|
在生命周期中用递归调用requestAnimationFrame,实现动画一直丝滑转运,但在销毁时要用cancelAnimationFrame终止执行。.png)
4.应用场景:滚动加载
在滚动事件中用requestAnimationFrame去加载渲染数据使混动效果更加丝滑。主要好久有几个
代码示例和效果如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| <template> <div class='container' ref='scrollRef'> <div v-for='(item, index) in items' :key='index' class='item'> {{ item }} </div> <div v-if='loading' class='loading'>数据加载中...</div> </div> </template>
<script setup lang='ts'> import { ref, onMounted, onUnmounted } from 'vue';
const loading = ref(false); let rafId: number | null = null; // 数据列表 const items = ref<string[]>(Array.from({ length: 50 }, (_, i) => `Test ${i + 1}`));
// 滚动容器 const scrollRef = ref<HTMLElement | null>(null);
// 模拟一个异步加载数据效果 const moreData = () => { return new Promise<void>((resolve) => { setTimeout(() => { const newItems = Array.from({ length: 50 }, (_, i) => `Test ${items.value.length + i + 1}`); items.value.push(...newItems); resolve(); }, 1000); }); };
// 检查是否需要加载更多数据 const checkScrollPosition = () => { if (loading.value) return;
const container = scrollRef.value; if (!container) return;
const scrollTop = container.scrollTop; const clientHeight = container.clientHeight; const scrollHeight = container.scrollHeight;
if (scrollHeight - scrollTop - clientHeight <= 100) { startLoading(); } };
// 加载数据 const startLoading = async () => { loading.value = true; await moreData(); loading.value = false; };
// 监听滚动事件 const handleScroll = () => { console.log('滚动事件触发啦'); if (rafId !== null) { window.cancelAnimationFrame(rafId); } rafId = window.requestAnimationFrame(checkScrollPosition); };
// 添加滚动事件监听器 onMounted(() => { if (scrollRef.value) { scrollRef.value.addEventListener('scroll', handleScroll); } });
// 移除相关事件 onUnmounted(() => { if (rafId !== null) { window.cancelAnimationFrame(rafId); } if (scrollRef.value) { scrollRef.value.removeEventListener('scroll', handleScroll); } }); </script>
<style scoped> .container { padding: 20px; max-width: 800px; overflow-y: auto; margin: 0 auto; height: 600px; }
.item { border-bottom: 1px solid #ccc; padding: 10px; }
.loading { padding: 10px; color: #999; text-align: center; } </style>
|
.png)
通过两个简单的事例可能大家会发现,只要在页面需要运动的地方其实都可以用到 requestAnimationFrame 使效果变的更加丝滑。例如游戏开发、各种动画效果和动态变化的布局等等。