为什么 requestAnimationFrame 比 setInterval 或 setTimeout 更好 [英] Why is requestAnimationFrame better than setInterval or setTimeout

查看:32
本文介绍了为什么 requestAnimationFrame 比 setInterval 或 setTimeout 更好的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么我应该使用 requestAnimationFrame 而不是 setTimeout 或 setInterval?

这个自我回答的问题是一个文档示例.

解决方案

高质量动画.

这个问题最简单的回答就是.requestAnimationFrame 产生更高质量的动画,完全消除使用 setTimeoutsetInterval 时可能发生的闪烁和剪切,并减少或完全消除跳帧.>剪切

是在显示扫描中途将新画布缓冲区呈现给显示缓冲区时,导致动画位置不匹配导致剪切线.

闪烁

在画布完全渲染之前将画布缓冲区呈现给显示缓冲区时引起的.

跳帧

是由于渲染帧之间的时间与显示硬件不精确同步造成的.每隔这么多帧就会跳过一帧,产生不一致的动画.(有一些方法可以减少这种情况,但我个人认为这些方法会产生更糟糕的整体结果)因为大多数设备使用每秒 60 帧(或多帧)导致每 16.666...ms 和计时器 setTimeout<产生一个新帧/code> 和 setInterval 使用整数值,它们永远无法完美匹配帧率(如果您有 interval = 1000/60,则四舍五入到 17ms)

<小时>

一个演示值一千字.

更新问题的答案requestAnimationFrame loop not correct fps 显示了 setTimeout 的帧时间不一致并将其与 requestAnimationFrame 进行比较.

该演示展示了一个简单的动画(条纹在屏幕上移动)单击鼠标按钮将在使用的渲染更新方法之间切换.

使用了多种更新方法.动画工件的确切外观将取决于您正在运行的硬件设置.你会在条纹的运动中寻找小抽搐

<块引用>

注意.您可能关闭了显示同步或关闭了硬件加速,这会影响所有计时方法的质量.低端设备也可能有动画问题

  • Timer 使用 setTimeout 来制作动画.时间是 1000/60
  • RAF 最佳质量,使用 requestAnimationFrame 制作动画
  • 双定时器 使用两个定时器,一个每 1000/60 次清除调用一次,另一个用于渲染.

    2019 年 10 月更新计时器呈现内容的方式发生了一些变化.为了表明 setInterval 没有正确地与显示刷新同步,我更改了 Dual timers 示例以表明使用多个 setInterval 仍然会导致严重闪烁的程度这将产生的闪烁取决于硬件设置.

  • 带有定时动画的 RAF,使用 requestAnimationFrame 但使用帧经过时间进行动画处理.这种技术在动画中很常见.我相信它是有缺陷的,但我把它留给观众

  • 带有定时动画的计时器.作为带定时动画的皇家空军",在这种情况下用于克服计时器"方法中看到的跳帧.我再次认为它很糟糕,但游戏社区发誓这是当您无法访问显示刷新时使用的最佳方法

/** SimpleFullCanvasMouse.js 开始 **/var backBuff;var bctx;const STRIPE_WIDTH = 250;var textWidth;const helpText = "单击鼠标更改渲染更新方法.";var onResize = function(){if(backBuff === 未定义){backBuff = document.createElement("canvas") ;bctx = backBuff.getContext("2d");}backBuff.width = canvas.width;backBuff.height = canvas.height;bctx.fillStyle = "白色"bctx.fillRect(0,0,w,h);bctx.fillStyle = "黑色";for(var i = 0; i  c(e));}if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}e.preventDefault();}m.updateBounds = function(){如果(m.active){m.bounds = m.element.getBoundingClientRect();}}m.addCallback = 函数(回调){if (typeof callback === "function") {if (m.callbacks === U) { m.callbacks = [回调];}else { m.callbacks.push(callback);}} else { throw new TypeError("mouse.addCallback 参数必须是一个函数");}}m.start = 函数(元素,blockContextMenu){if (m.element !== U) { m.removeMouse();}m.element = element === U ?文档:元素;m.blockContextMenu = blockContextMenu === U ?假:blockContextMenu;m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false);}m.active = 真;m.updateBounds();}m.remove = 函数 () {if (m.element !== U) {m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}m.element = m.callbacks = m.contextMenuBlocked = U;m.active = 假;}}返回鼠标;})();调整大小画布();鼠标开始(画布,真);调整大小()var lastTime = null;window.addEventListener("resize",resizeCanvas);函数 clearCTX(){ctx.setTransform(1,0,0,1,0,0);//重置变换ctx.globalAlpha = 1;//重置阿尔法ctx.clearRect(0,0,w,h);//虽然不需要,这是为了公平对待方法和演示闪烁}函数双更新(){如果(!dualTimersActive){dualTimersActive = 真;hdl1 = setInterval(clearCTX, 1000/60);hdl2 = setInterval(() => display(10), 1000/60);}}函数定时器更新(){计时器=性能.现在();如果(!最后一次){lastTime = 计时器;}var time = (timer-lastTime)/(1000/60);lastTime = 计时器;setTimeout(updateMethods[displayMethod],1000/60);clearCTX();显示(10*次);}函数 updateRAF(){clearCTX();requestAnimationFrame(updateMethods[displayMethod]);显示(10);}function updateRAFTimer(timer){//主更新循环clearCTX();requestAnimationFrame(updateMethods[displayMethod]);如果(!定时器){定时器 = 0;}如果(!最后一次){lastTime = 计时器;}var time = (timer-lastTime)/(1000/60);显示(10 * 时间);lastTime = 计时器;}显示方法 = 1;var updateMethods = [timerUpdate,updateRAF,dualUpdate,updateRAFTimer,timerUpdate]更新方法[显示方法]();/** SimpleFullCanvasMouse.js 结束 **/

Why should I use requestAnimationFrame rather than setTimeout or setInterval?

This self-answered question is a documentation example.

解决方案

High quality animation.

The question is most simply answered with. requestAnimationFrame produces higher quality animation completely eliminating flicker and shear that can happen when using setTimeout or setInterval, and reduce or completely remove frame skips.

Shear

is when a new canvas buffer is presented to the display buffer midway through the display scan resulting in a shear line caused by the mismatched animation positions.

Flicker

is caused when the canvas buffer is presented to the display buffer before the canvas has been fully rendered.

Frame skip

is caused when the time between rendering frames is not in precise sync with the display hardware. Every so many frames a frame will be skipped producing inconsistent animation. (There are method to reduce this but personally I think these methods produce worse overall results) As most devices use 60 frames per second (or multiple of) resulting in a new frame every 16.666...ms and the timers setTimeout and setInterval use integers values they can never perfectly match the framerate (rounding up to 17ms if you have interval = 1000/60)


A demo is worth a thousand words.

Update The answer to the question requestAnimationFrame loop not correct fps shows how setTimeout's frame time is inconsistent and compares it to requestAnimationFrame.

The demo shows a simple animation (stripes moving across the screen) clicking the mouse button will switch between the rendering update methods used.

There are several update methods used. It will depend on the hardware setup you are running as to what the exact appearance of the animation artifacts will be. You will be looking for little twitches in the movement of the stripes

Note. You may have display sync turned off, or hardware acceleration off which will affect the quality of all the timing methods. Low end devices may also have trouble with the animation

  • Timer Uses setTimeout to animate. Time is 1000/60
  • RAF Best Quality, Uses requestAnimationFrame to animate
  • Dual Timers, Uses two timers, one called every 1000/60 clears and another to render.

    UPDATE OCT 2019 There have been some changes in how timers present content. To show that setInterval does not correctly sync with the display refresh I have changed the Dual timers example to show that using more than one setInterval can still cause serious flicker The extent of the flickering this will produce depends on hardware set up.

  • RAF with timed animation, Uses requestAnimationFrame but animates using frame elapsed time. This technique is very common in animations. I believe it is flawed but I leave that up to the viewer

  • Timer with timed animation. As "RAF with timed animation" and is used in this case to overcome frame skip seen in "Timer" method. Again I think it suks, but the gaming community swear it is the best method to use when you don't have access to display refresh

/** SimpleFullCanvasMouse.js begin **/

var backBuff;
var bctx;
const STRIPE_WIDTH = 250;
var textWidth;
const helpText = "Click mouse to change render update method.";
var onResize = function(){
    if(backBuff === undefined){
        backBuff = document.createElement("canvas")    ;
        bctx = backBuff.getContext("2d");
        
    }
    
    backBuff.width = canvas.width;
    backBuff.height = canvas.height;
    bctx.fillStyle = "White"
    bctx.fillRect(0,0,w,h);
    bctx.fillStyle = "Black";
    for(var i = 0;  i < w; i += STRIPE_WIDTH){
        bctx.fillRect(i,0,STRIPE_WIDTH/2,h)   ;
        
    }
    ctx.font = "20px arial";
    ctx.textAlign = "center";
    ctx.font = "20px arial";
    textWidth = ctx.measureText(helpText).width;
    
};
var tick = 0;
var displayMethod = 0;
var methods = "Timer,RAF Best Quality,Dual Timers,RAF with timed animation,Timer with timed animation".split(",");
var dualTimersActive = false;
var hdl1, hdl2

function display(timeAdvance){  // put code in here

    tick += timeAdvance;
    tick %= w;


    ctx.drawImage(backBuff,tick-w,0);
    ctx.drawImage(backBuff,tick,0);
    if(textWidth !== undefined){
        ctx.fillStyle = "rgba(255,255,255,0.7)";
        ctx.fillRect(w /2 - textWidth/2, 0,textWidth,40);
        ctx.fillStyle = "black";
        ctx.fillText(helpText,w/2, 14);
        ctx.fillText("Display method : " + methods[displayMethod],w/2, 34);
    }
    if(mouse.buttonRaw&1){
        displayMethod += 1;
        displayMethod %= methods.length;
        mouse.buttonRaw = 0;
        lastTime = null;
        tick = 0;
        if(dualTimersActive) {
             dualTimersActive = false;
             clearInterval(hdl1);
             clearInterval(hdl2);
             updateMethods[displayMethod]()             
        }
    }
}








//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable 
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0; 
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
    if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d"); 
    if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; mouse.updateBounds(); }
mouse = (function(){
    function preventDefault(e) { e.preventDefault(); }
    var mouse = {
        x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3], 
        active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
    };
    var m = mouse;
    function mouseMove(e) {
        var t = e.type;
        m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
        m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
        if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }  
        else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
        else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
        else if (t === "mouseover") { m.over = true; }
        else if (t === "mousewheel") { m.w = e.wheelDelta; }
        else if (t === "DOMMouseScroll") { m.w = -e.detail; }
        if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
        if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}        
        e.preventDefault();
    }
    m.updateBounds = function(){
        if(m.active){
            m.bounds = m.element.getBoundingClientRect();
        }
        
    }
    m.addCallback = function (callback) {
        if (typeof callback === "function") {
            if (m.callbacks === U) { m.callbacks = [callback]; }
            else { m.callbacks.push(callback); }
        } else { throw new TypeError("mouse.addCallback argument must be a function"); }
    }
    m.start = function (element, blockContextMenu) {
        if (m.element !== U) { m.removeMouse(); }        
        m.element = element === U ? document : element;
        m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
        m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
        if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
        m.active = true;
        m.updateBounds();
    }
    m.remove = function () {
        if (m.element !== U) {
            m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
            if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
            m.element = m.callbacks = m.contextMenuBlocked = U;
            m.active = false;
        }
    }
    return mouse;
})();


resizeCanvas(); 
mouse.start(canvas,true); 
onResize()
var lastTime = null;
window.addEventListener("resize",resizeCanvas); 
function clearCTX(){
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.clearRect(0,0,w,h); // though not needed this is here to be fair across methods and demonstrat flicker
}



function dualUpdate(){
    if(!dualTimersActive) {
        dualTimersActive = true;
        hdl1 = setInterval( clearCTX, 1000/60);
        hdl2 = setInterval(() => display(10), 1000/60);
    }
}
function timerUpdate(){
    timer = performance.now();
    if(!lastTime){
        lastTime = timer;
    }
    var time = (timer-lastTime) / (1000/60);
    lastTime = timer;    
    setTimeout(updateMethods[displayMethod],1000/60);
    clearCTX();
    display(10*time);
}
function updateRAF(){ 
    clearCTX();
    requestAnimationFrame(updateMethods[displayMethod]);
    display(10);  
}
function updateRAFTimer(timer){ // Main update loop

    clearCTX();
    requestAnimationFrame(updateMethods[displayMethod]);
    if(!timer){
        timer = 0;
    }
    if(!lastTime){
        lastTime = timer;
    }
    var time = (timer-lastTime) / (1000/60);
    display(10 * time);  
    lastTime = timer;
}

displayMethod = 1;
var updateMethods = [timerUpdate,updateRAF,dualUpdate,updateRAFTimer,timerUpdate]
updateMethods[displayMethod]();

/** SimpleFullCanvasMouse.js end **/

这篇关于为什么 requestAnimationFrame 比 setInterval 或 setTimeout 更好的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆