稀土掘金技术社区 2024年12月24日
作为一个前端你连requestAnimationFrame的用法、优势和应用场景都搞不清楚?
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了requestAnimationFrame的用法及其在前端性能优化中的应用。文章首先介绍了requestAnimationFrame的基本概念和用法,强调其递归调用的重要性,并解释了它如何与显示器的刷新率同步,从而实现更流畅的动画效果。接着,文章对比了requestAnimationFrame与setTimeout/setInterval的差异,突出了前者在动画丝滑度和性能方面的优势。此外,文章还通过两个具体的应用场景:常规动画和滚动加载,展示了requestAnimationFrame如何提升用户体验。总结来说,requestAnimationFrame是前端开发中一个强大的工具,能有效提升页面性能和用户体验。

⏱️ `requestAnimationFrame` 核心优势在于其回调执行频率与显示器刷新率同步,能实现更流畅的动画效果,避免传统定时器可能出现的卡顿。

🔄 `requestAnimationFrame` 的正确用法是递归调用,而非单次调用,以保证动画的持续进行,并且它会返回一个时间戳参数,表示上一帧渲染的结束时间。

🚀 在性能方面,`requestAnimationFrame` 比 `setTimeout` 和 `setInterval` 更优,因为它能与浏览器绘制频率保持一致,并且在页面切换到后台时会暂停执行,从而节省资源。

🖼️ `requestAnimationFrame` 的应用场景非常广泛,包括但不限于常规动画(如图片旋转)和滚动加载,能有效提升用户体验。

📜 使用 `cancelAnimationFrame` 可以终止 `requestAnimationFrame` 的执行,类似于 `clearTimeout`,需要传入 `requestAnimationFrame` 返回的 ID。

原创 天天鸭 2024-12-24 08:30 重庆

点击关注公众号,“技术干货” 及时达!

前言

如果你是一名前端开发,那么你多少有了解过requestAnimationFrame吧?如果没有也接着往下看,会有详细用法说明。

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

requestAnimationFrame作用与用法

requestAnimationFrame简述

MDN官方说法是这样的」

基本示例

<script lang="ts" setup>    function init() {      console.log('您好,我是requestAnimationFrame');    }    requestAnimationFrame(init)</script>

「效果如下」

但是例子上面是最基本的调用方式,并且只简单执行了一次,而对于动画是要一直执行的。

下面直接上图看看官方的文档对这个的说明,上面说具体用法应该要递归调用,而不是单次调用。

递归调用示例

<script lang="ts" setup>    function init() {      console.log('您好,递归调用requestAnimationFrame');      requestAnimationFrame(init)    }    requestAnimationFrame(init)</script>

「执行动图效果如下」

requestAnimationFrame会一直递归调用执行,并且调用的频率通常是与当前显示器的刷新率相匹配(这也是这个API核心优势),例如屏幕75hz1秒执行75次。

而且如果使用的是定时器实现此功能是无法适应各种屏幕帧率的。

回调函数

requestAnimationFrame执行后的回调函数有且只会返回一个参数,并且返回的参数是一个毫秒数,这个参数所表示是的上一帧渲染的结束时间,直接看看下面代码示例与打印效果。

<script lang="ts" setup>  function init(val) {    console.log('您好,requestAnimationFrame回调:', val);    requestAnimationFrame(init);  }  requestAnimationFrame(init);</script>

「注意:」 如果我们同时调用了很多个requestAnimationFrame,那么他们会收到相同的时间戳,因为与屏幕的帧率相同所以并不会不一样。

终止执行

终止此API的执行,官方提供的方法是window.cancelAnimationFrame(),语法如下

ancelAnimationFrame(requestID)

直接看示例更便于理解,用法非常类似定时器的clearTimeout(),直接把 requestAnimationFrame 返回值传给 cancelAnimationFrame() 即可终止执行。

<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优势

动画更丝滑,不会出现卡顿

对比传统的setTimeoutsetInterval动画会更流畅丝滑。

主要 「原因」 是由于运行的浏览器会监听显示器返回的VSync信号确保同步,收到信号后再开始新的渲染周期,因此做到了与浏览器绘制频率绝对一致。所以帧率会相当平稳,例如显示屏60hz,那么会固定1000/60ms刷新一次。

但如果使用的是setTimeoutsetInterval来实现同样的动画效果,它们会受到事件队列宏任务、微任务影响会导致执行的优先级顺序有所差异,自然做不到与绘制同频。

所以使用setTimeoutsetInterval不但无法自动匹配显示屏帧率,也无法做到完全固定的时间去刷新。

性能更好,切后台会暂停

当我们把使用了requestAnimationFrame的页面切换到后台运行时,requestAnimationFrame会暂停执行从而提高性能,切换回来后会马上提着执行。

效果如下动图,隐藏后停止运行,切换回来接着运行。

应用场景:常规动画

用一个很简单的示例:用requestAnimationFrame使一张图片动态也丝滑旋转,直接看示例代码和效果。

「思路」:首先在页面初始化时执行window.requestAnimationFrame(animate)使动画动起来,实现动画一直丝滑转运。在关闭页面时用window.cancelAnimationFrame(rafId)去终止执行。

<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去加载渲染数据使混动效果更加丝滑。主要好久有几个

代码示例和效果如下:

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

看看下面动图效果

小结

通过代码示例配合动图讲解后,再通过两个简单的事例可能大家会发现,只要在页面需要运动的地方其实都可以用到 requestAnimationFrame 使效果变的更加丝滑。

除了上面两个小示例其它非常多地方都可以用到requestAnimationFrame去优化性能,比较常见的例如游戏开发、各种动画效果和动态变化的布局等等。

文章就写到这啦,如果文章写的哪里不对或者有什么建议欢迎指出。

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

requestAnimationFrame 前端性能优化 动画 滚动加载 JavaScript
相关文章