Pourquoi requestAnimationFrame est-il meilleur que setInterval ou setTimeout?

Pourquoi utiliser requestAnimationFrame plutôt que setTimeout ou setInterval?

cette question à réponse directe est un exemple de documentation.

27
demandé sur Graham 2016-08-02 03:15:56

1 réponses

animation de haute qualité.

la réponse à la question Est la plus simple. requestAnimationFrame produit une animation de qualité supérieure qui élimine complètement le scintillement et le cisaillement qui peuvent se produire en utilisant setTimeout ou setInterval, et de réduire ou de supprimer complètement les enjoliveurs.

Cisaillement

est quand un nouveau tampon de toile est présenté au tampon d'affichage à mi-chemin de la numérisation d'affichage résultant en une ligne de cisaillement causée par l'animation non appariée position.

Scintillement

est causé lorsque le tampon de toile est présenté au tampon d'affichage avant que la toile ait été entièrement rendue.

Frame skip

est causé lorsque le temps entre les images de rendu n'est pas synchronisé avec le matériel d'affichage. De toutes les images, une image sera sautée pour produire une animation incohérente. (Il y a une méthode pour réduire cela, mais personnellement je pense que ces méthodes produisent des résultats globaux plus mauvais) comme la plupart des appareils utilisent 60 images par seconde (ou plusieurs de) Résultant en un nouveau cadre tous les 16.666 ... ms et les minuteries setTimeout et setInterval utilisez des valeurs entières qu'ils ne peuvent jamais parfaitement correspondre au framerate (en arrondissant jusqu'à 17ms si vous avez interval = 1000/60)


Une démo vaut mille mots.

mise à Jour La réponse à la question requestAnimationFrame boucle pas correct fps montre comment le temps de trame de setTimeout est incohérent et le compare à requestAnimationFrame.

la démo montre une animation simple (rayures se déplaçant à travers l'écran) en cliquant sur le bouton de la souris, vous basculerez entre les méthodes de mise à jour du rendu utilisées.

il y a plusieurs méthodes de mise à jour utilisées. Cela dépendra de la configuration matérielle que vous exécuterez quant à l'apparence exacte des artefacts d'animation. Vous serez à la recherche de petites secousses dans le mouvement des rayures

Remarque. Vous peut avoir l'affichage de synchronisation désactivée, ou l'accélération matérielle qui affectent la qualité de tous le calendrier des méthodes. Les périphériques bas de gamme peuvent aussi avoir des problèmes avec l'animation

  • Timer utilise setTimeout pour animer. Le temps est de 1000/60
  • RAF Meilleure Qualité, utilise requestAnimationFrame pour animer
  • Double Minuteries, utilise deux minuteries, une appelée tous les 1000/60 efface et une autre pour rendre. L'étendue du clignotement que cela produira dépend de la configuration du matériel. Mais c'est mauvais et typique des solutions de rendu qui impliquent de nombreux événements, comme la souris, les minuteries, et ainsi de suite.
  • RAF avec animation chronométrée, utilise requestAnimationFrame mais s'anime en utilisant frame time écoulé. Cette technique est très courante dans les animations. Je crois qu'il est défectueux, mais je laisse le spectateur
  • Timer avec animation chronométrée. Comme " RAF avec chronométré animation" et est utilisé dans ce cas pour surmonter saut de cadre vu dans la méthode "Timer". Encore une fois, je pense qu'il suks, mais la communauté de jeu jurent qu'il est la meilleure méthode à utiliser lorsque vous n'avez pas accès à l'affichage actualiser

/** 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 **/
38
répondu Blindman67 2017-09-26 16:25:09