[解決済み] なぜrequestAnimationFrameはsetIntervalやsetTimeoutよりも優れているのでしょうか?
疑問点
setTimeoutやsetIntervalではなく、requestAnimationFrameを使うべき理由は何ですか?
この自問自答は、ドキュメントの例です。
どのように解決するのですか?
高画質なアニメーション。
という質問には、最もシンプルに答えることができます。
requestAnimationFrame
を使用した場合に起こりうるフリッカーやシアを完全に排除し、より高品質のアニメーションを生成します。
setTimeout
または
setInterval
に変更し、フレーム スキップを削減または完全に削除します。
は、新しいキャンバス バッファが表示スキャンの途中でディスプレイ バッファに提示され、アニメーションの位置の不一致によるシアー ラインが発生する場合です。
フリッカーは、キャンバスが完全にレンダリングされる前に、キャンバスバッファがディスプレイバッファに提示されたときに発生します。
フレーム スキップ
は、レンダリングフレーム間の時間がディスプレイハードウェアと正確に同期していないときに発生します。多くのフレームごとにフレームがスキップされ、一貫性のないアニメーションが作成されます。(ほとんどのデバイスは 60 フレーム/秒 (またはその倍数) を使用しているため、新しいフレームは 16.666...ms 毎になり、タイマは
setTimeout
と
setInterval
は整数値を使うので、フレームレートと完全に一致することはありません。
interval = 1000/60
)
デモは百聞は一見にしかず。
更新情報 質問に対する回答 requestAnimationFrameのループが正しいfpsにならない。 は、setTimeoutのフレーム時間がどのように一貫性がないかを示し、requestAnimationFrameと比較します。
このデモでは、簡単なアニメーション (画面上を移動する縞模様) を示しています。マウス ボタンをクリックすると、使用されるレンダリング更新方法が切り替わります。
使用される更新方法はいくつかあります。アニメーション アーティファクトの正確な外観がどのようになるかは、実行しているハードウェア設定に依存します。縞模様の動きに小さな動きがあるのがわかると思います。
注意。ディスプレイ同期がオフになっていたり、ハードウェア アクセラレーションがオフになっていると、すべてのタイミング方法の品質に影響を及ぼします。また、ローエンドのデバイスでは、アニメーションに問題が発生する可能性があります。
- タイマー setTimeoutを使用してアニメーションを行います。時間は1000/60
- RAF最高品質 アニメーションを行うためにrequestAnimationFrameを使用します。
-
デュアルタイマー ,
2つのタイマーを使用します。1つは1000/60クリアごとに呼ばれ、もう1つはレンダリングに呼ばれます。2019年8月更新 タイマーのコンテンツ表示方法について、一部変更がありました。 その表示方法として
setInterval
がディスプレイの更新と正しく同期していないことを示すために、デュアルタイマーの例を変更して、2 つ以上のsetInterval
を使用しても、深刻なフリッカーが発生する可能性があることを示すために、デュアル タイマーの例を変更しました。この場合に発生するフリッカーの程度は、ハードウェア セットアップに依存します。 -
時限式アニメーションによるRAF このテクニックは、requestAnimationFrameを使用しますが、フレームの経過時間を使用してアニメーションを行います。このテクニックはアニメーションでは非常に一般的です。私は欠陥があると信じていますが、それは視聴者に任せています。
- 時限式アニメーションを使ったタイマー . RAF with timed animationと同様で、この場合はTimer方式で見られるフレームスキップを克服するために使用されます。繰り返しになりますが、私はこれはひどいと思いますが、ゲームコミュニティは、ディスプレイのリフレッシュにアクセスできない場合に使用する最善の方法であると誓っています。
/** 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 **/
関連
-
[解決済み] なぜGoogleはJSONレスポンスにwhile(1);を前置するのでしょうか?
-
[解決済み] event.preventDefault() vs. return false
-
[解決済み] JSONPとは何か、なぜ作られたのか?
-
[解決済み] Node.jsを使うタイミングをどう判断するか?
-
[解決済み] varキーワードの目的と、どのような場合に使用する(または省略する)べきですか?
-
[解決済み] JavaScriptでsetIntervalの呼び出しを停止する
-
[解決済み] Node.jsのmodule.exportsの目的と使い方を教えてください。
-
[解決済み] setTimeout(fn, 0)が役に立つことがあるのはなぜですか?
-
[解決済み] setTimeoutかsetIntervalか?
-
[解決済み] jqueryはjavascriptのライブラリなのかフレームワークなのか?[クローズド]
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] なぜ "use strict "はパフォーマンスを10倍向上させるのか?
-
[解決済み] JavaScriptを使用してHTML要素に属性を追加/更新するには?
-
[解決済み] React js 親コンポーネントから子コンポーネントの状態を変更する
-
[解決済み] CORS OriginヘッダーとCSRFトークンによるCSRF保護
-
[解決済み] 文字列がhtmlであるかどうかをチェックする
-
[解決済み] AngularJS - ngRepeatフィルタリングされた結果の参照を取得する方法
-
[解決済み] Chromeの拡張機能開発にWebStormを使用するにはどうすればよいですか?
-
[解決済み] ECMAScriptとは?
-
[解決済み] JavaScriptの文字列プリミティブとStringオブジェクトの違いは何ですか?
-
[解決済み] Fetch: ステータスがOKでない場合、プロミスを拒否し、エラーをキャッチするか?