2016-08-02 114 views

回答

20

高品質的動畫。

這個問題是最簡單的回答。​​可以完全消除使用setTimeoutsetInterval時可能發生的閃爍和剪切,並減少或完全消除跳幀,從而生成更高質量的動畫。

剪切

是當一個新的畫布緩衝器通過導致引起錯配動畫位置的剪切線顯示掃描呈現給顯示緩衝器中途。

閃爍

是當畫布緩衝器被呈現給顯示緩衝器之前畫布已完全呈現引起的。

跳幀

當渲染幀之間的時間不與顯示硬件精確同步的原因。每幀如此多的幀將被跳過,產生不一致的動畫。 (有個方法可以減少這個,但我個人認爲這些方法會產生更糟糕的總體結果)由於大多數設備使用每秒60幀(或多個)導致新幀每16.666 ... ms和定時器setTimeoutsetInterval使用整數值他們永遠不能完全匹配的幀率(四捨五入到17MS如果你有interval = 1000/60


的演示是勝過千言萬語。

更新對問題requestAnimationFrame loop not correct fps的回答顯示setTimeout的幀時間如何不一致,並將其與requestAnimationFrame進行比較。

演示顯示一個簡單的動畫(條紋在屏幕上移動)單擊鼠標按鈕將在所使用的渲染更新方法之間切換。

有幾種更新方法使用。它將取決於您正在運行的硬件設置,以確定動畫構件的確切外觀。你會在條紋的移動中尋找小抽搐

注意。您可能會關閉顯示同步或關閉硬件加速,這會影響所有計時方法的質量。低端設備也可能有麻煩與動畫

  • 定時器使用setTimeout的動畫。時間是1000年至1060年
  • RAF最佳質量,使用requestAnimationFrame動畫
  • 雙定時器,使用了兩個定時器,一個叫每1000至1060年清空,並另一個渲染。這將產生閃爍的程度取決於硬件設置。但是,渲染解決方案涉及很多事件,比如鼠標,定時器和其他類型,這是很糟糕和典型的。
  • 帶定時動畫的RAF,使用requestAnimationFrame,但使用幀流逝的時間進行動畫處理。這種技術在動畫中非常常見。我相信它是有缺陷的,但我把它留給觀衆
  • 計時器與定時動畫。作爲「具有定時動畫的RAF」,並且在這種情況下用於克服在「定時器」方法中看到的跳幀。我再次認爲它suks,但遊戲社區發誓這是最好的方法,使用的時候你沒有訪問顯示刷新

/** 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 **/