requestAnimationFrame && setTimeout 原理剖析

就目前来讲,web应用实现动画的方式有很多种,排除CSS3自带的transitionanimation动画属性,这里讲一下JavaScript实现动画的其中两种方法,requestAnimationFrame && setTimeout

  • 前言

在开始分析之前,我们要了解几个概念,视觉暂留 屏幕刷新频率

1.视觉暂留

  眼睛的另一个重要特是视觉惰,即光象一旦在视网膜上形成,视觉将会对这个光象的感觉维持一个有限的时间,这种生理现象叫做视觉暂留。对于中等亮度的光刺激,视觉暂留时间约为50ms200ms。当我们看屏幕的时候,虽然你什么也没做,但是屏幕还是以特定的频率在不停刷新,只是这个刷新过程我们肉眼识别到他的细微变化,这就是我们接下来要说的 屏幕刷新频率

2.屏幕刷新频率

  我们日常的显示器,一般频率在60Hz左右,意味着我们的屏幕每1秒需要刷新60次,也就是说每1000ms需要更新60次的屏幕图像,那么我们由此可以得出,屏幕图像更新一次所需要的时间间隔也就是16.7ms(1000/60≈16.7)
  由于人的眼睛具有视觉暂留效应,且暂留时间为50ms200ms,也就是说人在看屏幕的时候,还没等到你的大脑印象消失,电脑屏幕就已经更新了,所以这个间隔让你感觉不到变化。
  那么屏幕刷新频率是不是越大越好?我们可以大胆假设一下,假如我有三个显示器,刷新频率分别为1Hz60Hz200Hz、那么对应的更新周期时间分别为1000ms16.7ms5ms也就是频率越大,图像更新的间隔就越短,我们看到的画面就会越稳定,当达到一秒更新一次的时候,这个时候我们就能够感觉到明显的屏幕闪烁,带来视觉疲劳。

显示器配置信息

3.setTimeout

  setTimeout说白了就是个延时计时器,通过设置固定的时间间隔,从而时间一到,执行相应的回调方法。这个过程不会考虑屏幕刷新频率,换句话讲,它的时间是写死的。那么为什么会存在丢帧现象发生,通俗来说就是为什么使用setTimeout我会感觉到卡顿,画面不稳定。

  1.setTimeout执行的时间与屏幕的刷新频率不一致会导致丢帧现象。我们不考虑异步问题,假设我们现在的屏幕设备是60Hz的刷新频率。那么我们图像的更新周期也就是16.7ms,我现在的动画要求是每10ms往下偏移1px,那么这个丢帧现象是如何产生的?这里我通过一张图来解释一下。

分析图

分析结果:
  • 第1次重绘(16.7ms):图形偏移到1px
  • 第2次重绘(33.4ms):图形偏移到3px丢失1px偏移;
  • 第3次重绘(50.1ms):图形偏移到4px
  • 第4次重绘(66.8ms):图形偏移到6px丢失1px偏移;
  • 第5次重绘(83.5ms):图形偏移到8px丢失1px偏移;
    ......
实验效果:
实验效果

  所以根据分析结果以及实验效果,如果setTimeout执行的顺序与屏幕的刷新频率不一致,会造成丢帧现象,从而视觉上带给我们的就是不流畅

  2.由于JavaScript属于单线程,而setTimeout任务会被放入异步队列,通俗来讲就是它的执行得等一等,具体等什么,不知道,就是想再等等。只有主线程的任务执行完毕之后,才会轮到它去执行,也就是说我虽然设置setTimeout 16.7ms间隔去执行动画属性改变,但是实际运行的时间可能会有所延迟。这个延迟可能会导致执行的时间与屏幕刷新的时间串掉,造成丢帧。

分析图

分析结果:
  • 第1次重绘(16.7ms):图形未偏移;应该偏移到1px
  • 第2次重绘(33.4ms):图形偏移到1px应该偏移到2px
  • 第3次重绘(50.1ms):图形偏移到2px;应该偏移到3px
  • 第4次重绘(66.8ms):图形偏移到3px应该偏移到4px
  • 第5次重绘(83.5ms):图形偏移到4px应该偏移到5px
    ......
      所以根据分析结果,我们可以看出,在异步的现象下,会造成一连串的执行差,从而造成丢帧现象

4.requestAnimationFrame

  官方解释window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。也就是说requestAnimationFrame它不需要你去手动设置执行间隔时间,它是跟随系统的屏幕刷新频率走的,如果屏幕刷新频率是60Hz,那么它的执行间隔就是16.7ms(1000/60≈16.7),如果屏幕刷新频率是100Hz,那么它的执行间隔就是10ms(1000/100=10),这样就能够保证它的执行与屏幕的刷新频率保持一致,从而避免丢帧现象。
  为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

5.requestAnimationFrame Vs setTimeout 区别

  • requestAnimationFrame在窗口隐藏的时候,会暂停调用,而setTimeout不会暂停。
  • requestAnimationFrame跟随系统屏幕刷新频率,而setTimeout手动配置,会存在丢帧现象。
代码示例
    var start = 0;
    var element = document.getElementById('SomeElementYouWantToAnimate');
    element.style.position = 'absolute';
    function step() {
        start++;
        element.style.top = start + 'px';
        if (start < 500) {
            window.requestAnimationFrame(step);
        }
    }
    window.requestAnimationFrame(step);

6.附件:requestAnimationFrame Github 兼容降级处理

  源地址:https://github.com/darius/requestAnimationFrame

// Adapted from https://gist.github.com/paulirish/1579671 which derived from 
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating

// requestAnimationFrame polyfill by Erik Möller.
// Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon

// MIT license

if (!Date.now)
    Date.now = function() { return new Date().getTime(); };

(function() {
    'use strict';
    
    var vendors = ['webkit', 'moz'];
    for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
        var vp = vendors[i];
        window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
        window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
                                   || window[vp+'CancelRequestAnimationFrame']);
    }
    if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
        || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
        var lastTime = 0;
        window.requestAnimationFrame = function(callback) {
            var now = Date.now();
            var nextTime = Math.max(lastTime + 16, now);
            return setTimeout(function() { callback(lastTime = nextTime); },
                              nextTime - now);
        };
        window.cancelAnimationFrame = clearTimeout;
    }
}());
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容