requestAnimationFrame详解-js性能优化

news2024/12/25 23:06:32

requestAnimationFrame 请求动画帧
它是一个浏览器的宏任务

requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行

requestAnimationFrame特点
【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,如果系统绘制率是 60Hz,那么回调函数就会16.7ms再 被执行一次,如果绘制频率是75Hz,那么这个间隔时间就变成了 1000/75=13.3ms。换句话说就是,requestAnimationFrame的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。

【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量

【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销

跟setTimeout和setInterva的对比
setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行

requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果

IE9-浏览器不支持该方法,可以使用setTimeout来兼容
 

//简单兼容
if (!window.requestAnimationFrame) {
    requestAnimationFrame = function(fn) {
        setTimeout(fn, 17);
    };    
}
//严格兼容 , 因为setTimeout内部运行也需要时间,以及需要给回调的第一个参数返回时间戳
if(!window.requestAnimationFrame){
    var lastTime = 0;
    window.requestAnimationFrame = function(callback){
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0,16.7-(currTime - lastTime));
        var id  = window.setTimeout(function(){
            callback(currTime + timeToCall);
        },timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    }
}

应用场景

1、监听 scroll 函数

页面滚动事件(scroll)的监听函数,就很适合用这个 api,推迟到下一次重新渲染。

$(window).on('scroll', function () {
  window.requestAnimationFrame(scrollHandler)
})

平滑滚动到页面顶部

const scrollToTop = () => { 
  const c = document.documentElement.scrollTop || document.body.scrollTop 
  if (c > 0) {  
    window.requestAnimationFrame(scrollToTop) 
    window.scrollTo(0, c - c / 8) 
  }
}

scrollToTop()

 

2、大量数据渲染

比如对十万条数据进行渲染,主要由以下几种方法:

(1)使用定时器

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) { 
  if (curTotal <= 0) {  
    return false 
  }  
  //每页多少条
  let pageCount = Math.min(curTotal, once) 
  setTimeout(() => {  
    for (let i = 0; i < pageCount; i++) { 
      let li = document.createElement('li')    
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)    
      ul.appendChild(li)  
    }  
    loop(curTotal - pageCount, curIndex + pageCount) 
  }, 0)
}
loop(total, index)

 (2)使用 requestAnimationFrame

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  //每页多少条
  let pageCount = Math.min(curTotal, once)
  window.requestAnimationFrame(function () {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
      ul.appendChild(li)
    }
    loop(curTotal - pageCount, curIndex + pageCount)
  })
}
loop(total, index)

 3、监控卡顿方法
每秒中计算一次网页的 FPS,获得一列数据,然后分析。通俗地解释就是,通过 requestAnimationFrame API 来定时执行一些 JS 代码,如果浏览器卡顿,无法很好地保证渲染的频率,1s 中 frame 无法达到 60 帧,即可间接地反映浏览器的渲染帧率。
 

var lastTime = performance.now()
var frame = 0
var lastFameTime = performance.now()
var loop = function (time) {
  var now = performance.now()
  var fs = now - lastFameTime
  lastFameTime = now
  var fps = Math.round(1000 / fs)
  frame++
  if (now > 1000 + lastTime) {
    var fps = Math.round((frame * 1000) / (now - lastTime))
    frame = 0
    lastTime = now
  }
  window.requestAnimationFrame(loop)
}

再附上个案例说明:

需求:用js实现一个无限循环的动画。

首先想到的是定时器

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
    setInterval(function(){
         render()
    },1000/60)
 
</script>
</body>
</html>

 

可以说是完美实现!

至于时间间隔为什么是1000/60,这是因为大多数屏幕渲染的时间间隔是每秒60帧。

既然setInterval可以搞定为啥还要用requestAnimationFrame呢?最直观的感觉就是,添加api的人是个大神级牛人,我只能怀疑自己。

所以搜索相关问题发现以下两点

requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:

1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。

2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

直接上代码:

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
 
    //requestAnimationFrame效果
    (function animloop() {
        render();
        window.requestAnimationFrame(animloop);
    })();
 
</script>
</body>
</html>

我没有添加各个浏览器的兼容写法,这里只说用法。

效果是实现了,不过我想到两个问题。

1、怎么停止requestAnimationFrame?是否有类似clearInterval这样的类似方法?

第一个问题:答案是确定的 必须有:cancelAnimationFrame()接收一个参数 requestAnimationFrame默认返回一个id,cancelAnimationFrame只需要传入这个id就可以停止了。

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    var rafId = null
 
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
 
    //requestAnimationFrame效果
    (function animloop(time) {
        console.log(time,Date.now())
        render();
        rafId = requestAnimationFrame(animloop);
        //如果left等于50 停止动画
        if(left == 50){
            cancelAnimationFrame(rafId)
        }
    })();
 
    //setInterval效果
    // setInterval(function(){
    //     render()
    // },1000/60)
 
</script>
</body>
</html>

 

 

2、如果我想动画频率降低怎么做,为什么不考虑加快呵呵 当前刷新频率已经是屏幕的刷新频率了再快也没有意义了

这个略微麻烦点

默认情况下,requestAnimationFrame执行频率是1000/60,大概是16ms多执一次。

如果我们想每50ms执行一次怎么办呢?

requestAnimationFrame执行条件类似递归调用 (说的是类似)别咬我,既然这样的话我们能否自定一个时间间隔再执行呢?当然定时器这么low的东西我们就不考虑了,都已经抛弃它用rAF了(都快结束了我才想起写简写太他妈长了),
这个思路来源于我几年前搞IM的一个项目,服务端推送消息为了减小包的大小不给时间戳,这个我们做前端的都知道,我们虽然很牛逼 不过用户更牛逼,万一改了时间就不好玩了。

解决方案是 当和服务端通信时 记录下一个时间差,(时间差等于服务端时间-本地时间)不管正负我们只要这个时间差。这样每当我们接受到消息 或者发送消息的时候我们就拿本地时间和是价差相加。这样就可以保证和服务端时间是一致的了,思路是不是很牛逼哈哈。

撤了半天我们通过以上思路来解决下rAF改变间隔的问题

上代码

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    //当前执行时间
    var nowTime = 0;
    //记录每次动画执行结束的时间
    var lastTime = Date.now();
    //我们自己定义的动画时间差值
    var diffTime = 40;
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
 
    //requestAnimationFrame效果
    (function animloop() {
        //记录当前时间
        nowTime = Date.now()
        // 当前时间-上次执行时间如果大于diffTime,那么执行动画,并更新上次执行时间
        if(nowTime-lastTime > diffTime){
            lastTime = nowTime
            render();
        }
        requestAnimationFrame(animloop);
 
    })()
</script>
</body>
</html>

效果: 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快鲸SCRM打通工单系统,实现客户售前售后一体化管理

深度运营客户关系&#xff0c;做好客户售后服务&#xff0c;才符合企业的长远利益。然而大多数企业只注重售前售中&#xff0c;忽视了售后&#xff0c;导致客户售后服务现状不尽人意&#xff0c;主要体现在&#xff1a; 把客户问题抛到售后群后&#xff0c;便放任不管; 缺乏标…

自动驾驶感知——超声波技术

文章目录1. 超声波基本概念1.1 声波的频率与分类1.2 超声波的波速和波长1.3 超声波的指向性1.4 超声波的反射和折射1.5 超声波的衰减1.6 超声波产生的效应2. 超声波传感器原理及传感器分类2.1 超声波传感器原理2.2 压电式超声波传感器2.3 磁致伸缩式超声波传感器2.4 超声波传感…

MyBatis案例 | 使用映射配置文件实现CRUD操作——通过主键查询对应数据

本专栏主要是记录学习完JavaSE后学习JavaWeb部分的一些知识点总结以及遇到的一些问题等&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址&#xff1a;&#x1f525;JavaWeb Java入门篇&#xff1a; &#x1f525;Java基础学习篇 Java进阶学习篇&…

关于Linux中断的相关查询

1.linux 内核 /proc/interrupts 在 /proc/interrupts 文件中记录了 Linux 内核的中断信息&#xff0c;我们可以通过命令查看 sudo cat /proc/interrupts 文件中以表格的形式列举出来所有的内核中断&#xff0c;其表头信息标注如下&#xff1a; 我们可以通过特定任务执行时…

计算机SCI论文一定要在指定的机构润色吗? - 易智编译EaseEditing

不一定要在指定的机构润色。 首先期刊要求润色&#xff0c;是非常正常的事情。国人投的中文论文&#xff0c;有的也会被要求润色。 更不要说国外的英文期刊了&#xff0c;咱们写的英文文章投过去&#xff0c;大部分都会被要求润色的。 为什么期刊总是要求润色语言呢&#xff…

Windows7操作系统安全(3)

实验简介 实验所属系列&#xff1a;网络安全实践 实验对象&#xff1a; 本科/专科信息安全专业 相关课程及专业&#xff1a;计算机基础&#xff0c;Linux基础 实验时数&#xff08;学分&#xff09;&#xff1a;2学时 实验类别&#xff1a;实践实验类预备知识 Windows系列是目…

Oracle重建控制文件

利用trace文件重建控制文件 1、生成trace文件&#xff1a; SQL>alter database backup controlfile to trace;2、找到生成的trace文件&#xff1a; SQL>show parameter user_dump_destuser_dump_dest 文件对应的路径即为trace文件的路径 3、关闭数据库 SQL>shutdo…

VueJS 之模板引用

文章目录参考描述模板引用引用访问模板引用组件中的模板引用$nextTick()示例updated错误示范正确演示$nextTick()参考 项目描述搜索引擎Bing哔哩哔哩黑马程序员VueJS 官方文档模板引用 描述 项目描述Edge109.0.1518.70 (正式版本) (64 位)操作系统Windows 10 专业版vue/cli5.…

jvm相关,jvm内存溢出,内存占用过高,CPU占用率高原因分析,MAT内存镜像文件分析的使用----学习笔记

什么是内存泄漏? 什么是内存溢出 内存溢出: OutOfMemory 它是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;抛出 OutOfMemory 错误 内存泄露: Memory Leak 它是指程序运行后&#xff0c;没有释放所占用的内存空间&#xff0c;比如程序运行完后没有释…

数据库和SQL初探

数据库和SQL初探1. 什么是数据库2. 数据库的结构3. MySQL表定义相关操作4. MySQL体系结构和存储引擎1. 什么是数据库 将大量数据保存起来&#xff0c;通过计算机加工而成的可以进行高效访问的数据集合。用来管理数据库的计算机系统称为数据库管理系统(DatabaseManagementSyste…

Java面试题二(自用-持续更新)

本文目录如下&#xff1a;Java面试题(二)四、并发编程线程和进程的区别&#xff1f;守护线程是什么&#xff1f;创建线程有哪几种方式&#xff1f;线程有哪些状态&#xff1f;sleep() 和 wait() 有什么区别&#xff1f;线程的sleep()方法和yield()方法有什么区别&#xff1f;线…

我发现买不起自己出版的书了,这到底是咋回事?

大家好&#xff0c;我是冰河~~ 这次我是真的有点买不起自己的书了&#xff01; 聊聊背景 继出版《海量数据处理与大数据技术实战》、《MySQL技术大全&#xff1a;开发、优化与运维实战》和《深入理解分布式事务&#xff1a;原理与实战》之后&#xff0c;冰河于2022年6月出版…

基于uni-app的小程序电子签名功能(带有笔锋)

前言 目前做的一个项目需要用到电子签名的功能&#xff0c;网上其实也挺多这种类型的电子签名&#xff0c;但是带有笔锋效果的确比较少&#xff0c;所以参考了一些博客&#xff0c;总结成了这个功能&#xff0c;在此分享给大家。 效果展示 代码展示 触摸开始&#xff08;touc…

大数据专业需要学习python么

如果零基础入门数据开发行业的小伙伴&#xff0c;可以从Python语言入手。 Python语言简单易懂&#xff0c;适合零基础入门&#xff0c;在编程语言排名上升最快&#xff0c;能完成数据挖掘、机器学习、实时计算在内的各种大数据集成任务。 但是不会python也是没有关系的&#…

基于Vector VT系统的车身域测试方案介绍

车身域控制器作为车身主要部件的控制大脑&#xff0c;需要经过严苛的测试&#xff0c;常见的测试方式是通过搭建硬件在环测试系统实现的&#xff0c;测试原理是通过仿真控制器外围的传感器和执行器&#xff0c;实现车身域控制器的闭环仿真和测试。小怿基于国际一流的测试设备提…

Day15 C++STL入门基础知识九——list容器 基本概念-构造函数-赋值变换-大小操作-插入删除-数据存取-反转排序 【全面深度剖析+例题代码展示】

文章目录1. 基本概念1.1 功能1.2 概念1.3 组成和存储方式1.4 优缺点1.4 图解2. 构造函数2.1 函数原型2.2 代码展示2.3 测试结果3. 赋值交换3.1 函数原型3.2 代码展示3.3 测试结果4. 大小操作1.3 代码展示1.4 测试结果6. 插入删除6.1 函数原型6.2 代码展示6.3 测试结果7. 数据存…

React报错#310复盘小结

React报错#310复盘小结问题背景解决方案原理&学习引发错误情况1. 不要在循环&#xff0c;条件或嵌套函数中调用 Hook2. 把所有的钩子移到组件的顶层&#xff0c;在任何可能返回值的条件之上。总结问题背景 apm报错&#xff1a;Minified React error #310 https://reactjs.…

浅谈Graph Embedding(一)

Graph Embedding算法背景引入先回顾下图的概念&#xff1a;图(graph)由节点(vertex)和点之间连线(edge)所组成&#xff1b;其中&#xff0c;点通常被成为“顶点(vertex)”,而点与点之间的连线则被成为“边”(edge)。通常记为,G(V,E)。常见分为无向图和有向图。示例如下&#xf…

MySQL数据类型约束

文章目录数据类型MySQL中的数据类型整数类型类型介绍可选属性MUNSIGNEDZEROFILL适用场景如何选择&#xff1f;浮点类型类型介绍数据精度说明精度误差说明定点数类型类型介绍开发中经验位类型&#xff1a;BIT6. 日期与时间类型YEAR类型DATE类型TIME类型DATETIME类型TIMESTAMP类型…

小白入门pwn笔记 CPU与进程的执行

1.回顾存储在磁盘中的叫节&#xff0c;映射到内存的时候叫段。内存中的节根据读写执行的权限不同在内存中映像为不同的段。段视图用于进程的内存区域的rwx权限划分。节视图用于ELF文件编译链接时与在磁盘上储存时的文件结构的组织。2.代码在内存重的映射关系不可写的数据一般会…