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

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

问题描述

为什么要使用requestAnimationFrame而不是setTimeout或setInterval?



这个自我回答的问题是作为stackoverflow文档示例的一部分为动画循环使用requestanimationframe not setinterval

解决方案

高品质的动画。



最简单的回答。 requestAnimationFrame 产生更高质量的动画,完全消除使用 setTimeout setInterval时可能发生的闪烁和剪切



剪切

新的画布缓冲器通过显示扫描中途被提供给显示缓冲器,导致由不匹配的动画位置引起的剪切线。



闪烁



跳过 $ b
$ b

是在渲染帧之间的时间与显示硬件不精确同步时引起的。每一个这么多帧,一个帧将被跳过,产生不一致的动画。 (有方法减少这个,但个人我认为这些方法产生更糟糕的总体结果)因为大多数设备使用60帧每秒(或多个),导致一个新的帧每16.666 ...毫秒和定时器 setTimeout setInterval 使用整数值,它们永远不能完美匹配帧速率(如果您有 interval = 1000/60






一个演示是千言万语的。



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



使用了几种更新方法。它将取决于您正在运行的硬件设置,以确定动画制品的确切外观。


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





  • 计时器使用setTimeout动画。时间为1000/60

  • RAF最佳品质,使用requestAnimationFrame设定动画

  • >,使用两个计时器,一个调用每1000/60个清除,另一个调用渲染。闪烁的程度取决于硬件设置。

  • 具有定时动画的RAF ,使用requestAnimationFrame,但动画使用帧经过时间。这种技术在动画中非常常见。我相信这是有缺陷的,但我留给观众

  • 计时器与定时动画。作为具有定时动画的RAF并且在这种情况下用于克服在定时器方法中看到的帧跳过。再次,我认为它是suks,但游戏社区发誓,这是当你没有访问显示刷新的最佳方法



这个自我回答的问题是作为stackoverflow文档示例的一部分为动画循环使用requestanimationframe not setinterval



  / ** SimpleFullCanvasMouse.js begin ** / var backBuff; var bctx; const STRIPE_WIDTH = 250; var textWidth; const helpText =点击鼠标更改渲染更新方法; var onResize = function(){if(backBuff === undefined){backBuff = document.createElement(canvas); bctx = backBuff.getContext(2d); } backBuff.width = canvas.width; backBuff.height = canvas.height; bctx.fillStyle =Whitebctx.fillRect(0,0,w,h); bctx.fillStyle =Black; for(var i = 0; i  


Why should I use requestAnimationFrame rather than setTimeout or setInterval?

This self answered question is as part of a stackoverflow documentation example Use requestanimationframe not setinterval for animation loop

解决方案

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.

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. The extent of the flickering this will produce depends on hardware set up. But it is bad and typical of rendering solutions that involve many events, like mouse, timers, and whatnot.
  • 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

This self answered question is as part of a stackoverflow documentation example Use requestanimationframe not setinterval for animation loop

/** 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(",");

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








//==================================================================================================
// 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(){
    setTimeout(updateMethods[displayMethod],1000/60);
    clearCTX();
    setTimeout(function(){
        display(10);
    },0);    
}
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天全站免登陆