JS动画三剑客——setTimeout、setInterval、requestAnimationFrame
一、前言
前端实现动画效果主要有以下几种方法:CSS3中的transition 和 animation ,Javascript 中可以通过定时器 setTimeout、setinterval,HTML5 canvas,HTML5提供的requestAnimationFrame。本文主要分析setTimeout、setinterval、requestAnimationFrame三者的区别和他们各自的优缺点。在了解他们三个之前,我们先来看看一些相关概念。
二、相关概念介绍
1.屏幕刷新频率
即图像在屏幕上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。 对于一般笔记本电脑,这个频率大概是60Hz。这个值的设定受屏幕分辨率、屏幕尺寸和显卡的影响。
2.动画原理
动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。在屏幕每次刷新前,将图像的位置向左移动一个像素,即1px。屏幕每次刷出来的图像位置都比前一个要差1px,你就会看到图像在移动;由于我们人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此你才会看到图像在流畅的移动,这就是视觉效果上形成的动画。
三、setInterval
1.运行机制
按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数(当页面被隐藏或者最小化时,setInterval()仍在后台继续执行,这种动画刷新是完全没有意义的,对cpu也是极大的浪费),直到 clearInterval() 被调用或窗口被关闭。
setinterval的执行时间不确定,参数中的时间间隔是将代码添加到异步队列中等待的时间。只有当主线程中的任务以及队列前面的任务是执行完毕,才真正开始执行动画代码。
注:HTML5标准规定,setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒。
2.语法
setinterval(code, milliseconds);
setinterval(function, milliseconds, param1, param2, ...)
| 参数 | 描述 |
|---|---|
| code/function | 必需。要调用一个代码串,也可以是一个函数。 |
| milliseconds | 必须。周期性执行或调用 code/function 之间的时间间隔,以毫秒计。 |
| param1, param2, ... | 可选。 传给执行函数的其他参数(IE9 及其更早版本不支持该参数)。 |
3.实例
//每三秒(3000 毫秒)弹出 "Hello":
var myVar; function myFunction() {
myVar = setInterval(alertFunc, 3000);
} function alertFunc() {
alert("Hello!");
}
4.清除setInterval
clearinterval() 方法可取消由 setinterval() 函数设定的定时执行操作。参数必须是由 setinterval() 返回的 id 值。 注意: 要使用 clearinterval() 方法, 在创建执行定时操作时要使用全局变量.清除示例如下:
var myVar = setInterval(function(){ setColor() }, 300);
function setColor() {
var x = document.body;
x.style.backgroundColor = x.style.backgroundColor == "yellow" ? "pink" : "yellow";
}
function stopColor() {
clearInterval(myVar);
}
5.缺点
(1)setinterval()无视代码错误,如果setinterval执行的代码由于某种原因出了错,它还会持续不断地调用该代码。
(2)setinterval无视网络延迟,由于某些原因(服务器过载、临时断网、流量剧增、用户带宽受限,等等),你的请求要花的时间远比你想象的要长。但setinterval不在乎。它仍然会按定时持续不断地触发请求,最终你的客户端网络队列会塞满调用函数。
(3) setinterval不保证执行,与settimeout不同,并不能保证到了时间间隔,代码就准能执行。如果你调用的函数需要花很长时间才能完成,那某些调用会被直接忽略
四、setTimeout
1.运行机制
在指定的毫秒数后调用函数或计算表达式。每次函数执行的时候都会创建换一个新的定时器。在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何确实的间隔。并且确保在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。当方法执行完成定时器就立即停止(但是定时器还在,只不过没用了);
2.语法(同setInterval)
3.实例
//3 秒(3000 毫秒)后弹出 "Hello" :
var myVar; function myFunction() {
myVar = setTimeout(alertFunc, 3000);
} function alertFunc() {
alert("Hello!");
}
4.清除setTimeout
使用cleartimeout函数,用法同clearinterval
5.缺点
(1)利用seTimeout实现的动画在某些低端机上会出现卡顿、抖动的现象。
(2)settimeout的执行时间并不是确定的。在javascript中, settimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 settimeout 的实际执行时间一般要比其设定的时间晚一些。
(3)刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 settimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
(4)settimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像。
五、requestAnimationFrame(推荐使用)
1.运行机制
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。不需要设置时间间隔,是由系统的时间间隔定义的。大多数浏览器的刷新频率是60Hz(每秒钟反复绘制60次),循环间隔是1000/60,约等于16.7ms。不需要调用者指定帧速率,浏览器会自行决定最佳的帧效率。只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
2.语法
window.requestanimationframe(callback);
参数callback:下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。
3.实例
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute'; function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) {
window.requestAnimationFrame(step);
}
} window.requestAnimationFrame(step);
4.缺点
requestanimationframe 不管理回调函数,即在回调被执行前,多次调用带有同一回调函数的 requestanimationframe,会导致回调在同一帧中执行多次。我们可以通过一个简单的例子模拟在同一帧内多次调用 requestanimationframe 的场景:(mousemove, scroll 这类事件常见)
const animation = timestamp => console.log('animation called at', timestamp)
window.requestAnimationFrame(animation)
window.requestAnimationFrame(animation)
// animation called at 320.7559999991645
// animation called at 320.7559999991645
我们用连续调用两次 requestanimationframe 模拟在同一帧中调用两次 requestanimationframe。 例子中的 timestamp 是由 requestanimationframe 传给回调函数的,表示回调队列被触发的时间。由输出可知,animation 函数在同一帧内被执行了两次,即绘制了两次动画。
ps:解决办法
对于这种高频发事件,一般的解决方法是使用节流函数。但是在这里使用节流函数并不能完美解决问题。因为节流函数是通过时间管理队列的,而 requestanimationframe 的触发时间是不固定的,在高刷新频率的显示屏上时间会小于 16.67ms,页面如果被推入后台,时间可能大于 16.67ms。
完美的解决方案是通过 requestanimationframe 来管理队列,其思路就是保证 requestanimationframe 的队列里,同样的回调函数只有一个。示例代码如下:
const onScroll = e => {
if (scheduledAnimationFrame) { return }
scheduledAnimationFrame = true
window.requestAnimationFrame(timestamp => {
scheduledAnimationFrame = false
animation(timestamp)
})
}
window.addEventListener('scroll', onScroll)
5.与setTimeout和setInterval的区别
(1)requestanimationframe会把每一帧中的所有dom操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
(2)在隐藏或不可见的元素中,requestanimationframe将不会进行重绘或回流,这当然就意味着更少的cpu、gpu和内存使用量
(3)requestanimationframe是由浏览器专门为动画提供的api,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了cpu开销
6.兼容性封装
if(!window.requestAnimationFrame) {
window.requestAnimationFrame = (window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
var self = this, start, finish;
return window.setTimeout(function() {
start = +new Date();
callback(start);
finish = +new Date();
self.timeout = 1000/60 - (finish - start);
}, self.timeout);
});
}
代码解析:
这段代码先检查了 window.requestanimationframe 函数的定义是否存在。如果不存在,就遍历已知的各种浏览器实现并替代该函数。如果还是找不到一个与浏览器相关的实现,它最终会采用基于javascript定时器的动画以每秒60帧的间隔调用settimeout函数。
mozrequestanimationframe() 会接收一个时间码(从1970年1月1日起至今的毫秒数),表示下一次重绘的实际发生时间。这样, mozrequestanimationframe() 就会根据这个时间码设定将来的某个时刻进行重绘。
但是 webkitrequestanimationframe() 和 msrequestanimationframe() 不会给回调函数传递时间码,因此无法知道下一次重绘将发生在什么时间。 如果要计算两次重绘的时间间隔,firefox中可以使用既有的时间码,而在chrome和ie则可以使用不太精确地date()对象。
7.清除动画
cancelAnimationFrame(动画名) ,类似clearTimeout函数

六、总结
1.执行次数:setInterval执行多次,setTimeout、requestAnimationframe执行一次
2.性能:setTimeout会出现丢帧、卡顿现象,setInterval会出现调用丢失情况,requestAnimationframe不会出现这些问题,页面未激活时不会执行动画,减少了大量cpu消耗
3.兼容性问题:setInterval,setTimeout在IE浏览器中不支持参数传递,能够在大多数浏览器中正常使用。而requestAnimationframe不兼容IE10以下
七、面试题
1.setTimeout中的this指向问题
var i = 0;
const o = {
i: 1;
fn: function(){
console.log(this.i);
}
}
setTimeout(o.fn, 1000); //执行后会打印出什么
错误思路:setTimeout执行,调用对象O的fn函数,由于调用者是对象O,那么this也指向了对象O,又对象O中有属性i,则会打印出1。
正解:因为setTimeout是window对象的方法,传入o.fn只是将o.fn这个函数传给了setTimeout,仍然是window对象在调用。上面代码执行的正确结果是0,是因为定义了全局变量i为0。如果没有定义,则会输出undefined。
ps:如果这里不是setTimeout执行这个函数,而是o.fn(),那么会输出1。
2.执行下面的代码,控制台如何输出
(function () {
setTimeout(function () {
alert(2);
}, 0);
alert(1);
})()
先弹出的应该是1,而不是你以为“立即执行”的2。 settimeout,setinterval都存在一个最小延迟的问题,虽然你给的delay值为0,但是浏览器执行的是自己的最小值。html5标准是4ms,但并不意味着所有浏览器都会遵循这个标准,包括手机浏览器在内,这个最小值既有可能小于4ms也有可能大于4ms。在标准中,如果在settimeout中嵌套一个settimeout, 那么嵌套的settimeout的最小延迟为10ms。
3.执行下面的代码,控制台输出什么
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
输出结果大家都只是会是5个6,由于JavaScript是单线程的,按顺序执行,setTimeout是异步函数,它会将 timer 函数放到任务队列中,而此时会先将循环执行完毕再执行 timer 函数,因此当执行 timer 函数时 i 已经等于6了,所以最终会输出5个6
ps:解决办法有三种,我只贴代码了
//闭包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
} //给setTimeout传参
//方式一 IE不支持
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
//方式二 for (var i = 1; i <= 5; i++) {
(function(i){
setTimeout(function(){
console.log(i)
},i * 1000)
})(i)
}
//ES6 let
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
4.使用settimeout代替setinterval进行间歇调用
var executeTimes = 0;
var intervalTime = 500;
var intervalId = null; // 放开下面的注释运行setInterval的Demo
intervalId = setInterval(intervalFun,intervalTime);
// 放开下面的注释运行setTimeout的Demo
// setTimeout(timeOutFun,intervalTime); function intervalFun(){
executeTimes++;
console.log("doIntervalFun——"+executeTimes);
if(executeTimes==5){
clearInterval(intervalId);
}
} function timeOutFun(){
executeTimes++;
console.log("doTimeOutFun——"+executeTimes);
if(executeTimes<5){
setTimeout(arguments.callee,intervalTime);
}
}
代码比较简单,我们只是在settimeout的方法里面又调用了一次settimeout,就可以达到间歇调用的目的。 setinterval间歇调用,是在前一个方法执行前,就开始计时,比如间歇时间是500ms,那么不管那时候前一个方法是否已经执行完毕,都会把后一个方法放入执行的序列中。这时候就会发生一个问题,假如前一个方法的执行时间超过500ms,加入是1000ms,那么就意味着,前一个方法执行结束后,后一个方法马上就会执行,因为此时间歇时间已经超过500ms了。
5.利用settimeout来实现setinterval
function interval(func, w, t){
var interv = function(){
if(typeof t === "undefined" || t-- > 0){
setTimeout(interv, w);
try{
func.call(null);
}
catch(e){
t = 0;
throw e.toString();
}
}
};
setTimeout(interv, w);
};
参考文档:https://blog.csdn.net/weixin_34204057/article/details/89009605
http://www.luyixian.cn/javascript_show_149688.aspx
https://juejin.im/post/5c89fe42e51d455bb15c1ed1
https://www.cnblogs.com/icctuan/p/12103697.html
JS动画三剑客——setTimeout、setInterval、requestAnimationFrame的更多相关文章
- js 定时器(setTimeout/setInterval)出现变量未定义(xxx is not defined) 的解决方法
首先声明本人资质尚浅,如有错误,欢迎指正.共同提高. ------------------------------------------------------------------------- ...
- js动画最佳实现——requestAnimationFrame
我们经常用setInterval来实现动画,其实这种做法不是太好,因为不同浏览器的刷新频率也不一样(一般认为设置16为最佳,按每秒60帧算,1000/60≍16.67) var dis = 0,tim ...
- Web Animations API (JS动画利器)
原文地址:→传送门 写在前面 之前学习了CSS animation/setTimeout/setInterval/requestAnimationFrame等,这些都可以用在某种场景下的小动画,也可以 ...
- 性能更好的js动画实现方式——requestAnimationFrame
用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,css3动画出来后,我们又可以使用css3来实现动画了,而且性能和流畅度也得到了很大的提升.但是css3动画还是 ...
- 性能更好的js动画实现方式——requestAnimationFrame
本文转载,原文地址:http://www.cnblogs.com/2050/p/3871517.html 用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,css ...
- 性能更好的js动画实现方式---requestAnimationFrame
用js来实现动画,我们一般是借助setTimeout或setInterval这两个函数,css3动画出来后,我们又可以使用css3来实现动画了,而且性能和流畅度也得到了很大的提升.但是css3动画还是 ...
- js动画之requestAnimationFrame
1.setTimeout和setInterval 在讲setTimeout和setInterval之前,先讲一下异步执行的运行机制.(同步执行也是如此,因为它可以被视为没有异步任务的异步执行.) (1 ...
- js里的setTimeout和setInterval之后的页面是空白,阻塞浏览器的document对象,但是不阻塞script方法
js里的setTimeout和setInterval是否进程阻塞? 阻塞浏览器的document对象,但是不阻塞script方法 当你在setTimeout中使用document.write时是不行的 ...
- js中setTimeout/setInterval定时器用法示例
js中setTimeout(定时执行一次)和setInterval(间隔循环执行)用法介绍. setTimeout:在指定的毫秒数后调用指定的代码段或函数:setTimeout示例代码 functio ...
随机推荐
- Java实现 LeetCode 594 最长和谐子序列(滑动窗口)
594. 最长和谐子序列 和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1. 现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度. 示例 1: 输入: [1,3, ...
- Java实现 蓝桥杯VIP 算法训练 星际交流
算法训练 星际交流 时间限制:1.0s 内存限制:256.0MB 问题描述 人类终于登上了火星的土地并且见到了神秘的火星人.人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法 ...
- Linux文件处理命令 ls 详解
Linux系统的应用场景最多的就是用作服务器的系统了,简洁,安全,高效,一般我们服务器端不会安装Linux的图形化界面,虽然现在一些Linux发行版的图形界面也很漂亮,但是,服务器最主要的是高效.所以 ...
- JS变量小总
变量分类:1.栈内存(stack)和堆内存(heap)2.基本类型和引用类型 #栈内存(stack) 一般为静态分配内存,其分配的内存系统自动释放. #堆内存(heap) 一般为动态分配内存,其分配的 ...
- 调优 | Apache Hudi应用调优指南
通过Spark作业将数据写入Hudi时,Spark应用的调优技巧也适用于此.如果要提高性能或可靠性,请牢记以下几点. 输入并行性:Hudi对输入进行分区默认并发度为1500,以确保每个Spark分区都 ...
- k8s-ephemeral和init容器
截止到目前k8s1.18版本,k8s已经支持四种类型的container:标准容器,sidecar容器,init容器,ephemeral容器. 一:ephemeral容器 1.1.什么是ephemer ...
- Cookie默认不设置path时,哪些请求会携带cookie数据
默认不设置path的时候,只会在请求和servlet同路径的情况下才会携带cookie中存储的数据,包含同级目录和下级目录 例如: 在http://localhost:8080/day01/test/ ...
- 别在重复造轮子了,几个值得应用到项目中的 Java 开源库送给你
我是风筝,公众号「古时的风筝」.文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面.公众号回复『666』获取高清大图. 风筝我作为一个野路子开发者,直到 ...
- c#撸的控制台版2048小游戏
1.分析 最近心血来潮,突然想写一个2048小游戏.于是搜索了一个在线2048玩玩,熟悉熟悉规则. 只谈核心规则:(以左移为例) 1.1合并 以行为单位,忽略0位,每列依次向左进行合并,且每列只能合并 ...
- el-table的花样需求---表格加图片、加音频、加序号、多级动态表头
elemnet-ui组件库大家应该不陌生,在展示多条结构类似的数据方面,el-table可谓扛把子,不仅可以把数据展示的整齐,还支持排序.筛选或其他自定义操作.那么,除了上述的基本功能外,你还遇到过哪 ...