requestAnimationFrame이 setInterval 또는 setTimeout보다 나은 이유
setTimeout 또는 setInterval 대신 requestAnimationFrame을 사용해야하는 이유는 무엇입니까?
이 자체 답변 질문은 문서화 예입니다.
고품질 애니메이션.
질문은 가장 간단하게 답변됩니다. requestAnimationFrame
높은 품질의 애니메이션을 완전히 제거 깜박임을 생산하고 사용할 때 발생할 수있는 전단 setTimeout
이나 setInterval
, 및 줄이거 나 완전히 프레임 스킵을 제거합니다.
새 캔버스 버퍼가 디스플레이 스캔 중간에 디스플레이 버퍼에 제공되어 일치하지 않는 애니메이션 위치로 인해 전단 라인이 발생하는 경우입니다.
깜박임캔버스가 완전히 렌더링되기 전에 캔버스 버퍼가 디스플레이 버퍼에 제공 될 때 발생합니다.
프레임 건너 뛰기렌더링 프레임 사이의 시간이 디스플레이 하드웨어와 정확하게 동기화되지 않을 때 발생합니다. 너무 많은 프레임마다 프레임을 건너 뛰고 일관성없는 애니메이션이 생성됩니다. 타이머 (이 감소하는 방법이 있지만 개인적으로 나는이 방법이 더 나쁜 전반적인 결과를 생각) 대부분의 장치가 두 번째 (또는 여러 개) 당 60 개 프레임을 사용할 때마다 16.666 ... MS의 새로운 프레임의 결과와 setTimeout
및 setInterval
사용 정수 값들은 프레임 속도와 완벽하게 일치 할 수 없습니다 (있는 경우 최대 17ms까지 반올림 interval = 1000/60
)
데모는 천 단어의 가치가 있습니다.
업데이트 질문 requestAnimationFrame 루프가 올바른 fps가 아님에 대한 답변 은 setTimeout의 프레임 시간이 어떻게 일치하지 않고 requestAnimationFrame과 비교되는지 보여줍니다.
데모는 간단한 애니메이션 (화면을 가로 지르는 줄무늬)을 보여줍니다. 마우스 버튼을 클릭하면 사용 된 렌더링 업데이트 방법간에 전환됩니다.
몇 가지 업데이트 방법이 사용됩니다. 애니메이션 아티팩트의 정확한 모양은 실행중인 하드웨어 설정에 따라 다릅니다. 줄무늬의 움직임에 작은 트 위치를 찾을 것입니다.
노트. 디스플레이 동기화를 끄거나 하드웨어 가속을 꺼 모든 타이밍 방법의 품질에 영향을 미칠 수 있습니다. 저가형 장치도 애니메이션에 문제가있을 수 있습니다.
- Timer 애니메이션에 setTimeout을 사용합니다. 시간은 1000/60입니다.
- RAF Best Quality , requestAnimationFrame을 사용하여 애니메이션
- 듀얼 타이머 , 두 개의 타이머를 사용합니다. 하나는 1000/60 클리어마다 호출되고 다른 하나는 렌더링에 호출됩니다. 이로 인해 발생하는 깜박임의 정도는 하드웨어 설정에 따라 다릅니다. 그러나 마우스, 타이머 및 기타와 같은 많은 이벤트를 포함하는 렌더링 솔루션의 경우 나쁘고 일반적입니다.
- 시간이 지정된 애니메이션이있는 RAF , requestAnimationFrame을 사용하지만 프레임 경과 시간을 사용하여 애니메이션합니다. 이 기술은 애니메이션에서 매우 일반적입니다. 결함이 있다고 생각하지만 시청자에게 맡겨
- 시간이 지정된 애니메이션이있는 타이머 . "시간이 지정된 애니메이션이있는 RAF"로이 경우 "타이머"방식에서 볼 수있는 프레임 스킵을 극복하기 위해 사용됩니다. 다시 말하지만, 게임 커뮤니티는 디스플레이 새로 고침에 대한 액세스 권한이 없을 때 사용하는 가장 좋은 방법이라고 맹세합니다.
/** 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 **/
ReferenceURL : https://stackoverflow.com/questions/38709923/why-is-requestanimationframe-better-than-setinterval-or-settimeout
'programing' 카테고리의 다른 글
"from __future__ import braces"코드는 어디에 있습니까? (0) | 2021.01.15 |
---|---|
깊이 1로 특정 커밋을 얕은 복제하는 방법은 무엇입니까? (0) | 2021.01.15 |
데이터베이스에서 고유 한 인덱싱 된 열 값 교체 (0) | 2021.01.15 |
뮤텍스는 어떻게 구현됩니까? (0) | 2021.01.15 |
Eclipse의 유효한 HTML5 속성에 대한 경고 (0) | 2021.01.15 |