質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.50%

描画のタイミングを最適化する、とは

解決済

回答 3

投稿

  • 評価
  • クリップ 3
  • VIEW 7,208

score 481

setTimeoutメソッドを利用すると、「指定した間隔」と「ブラウザが画面を更新するタイミング」に差が生じることになります。そうなると、ブラウザが一度画面を更新する間に、何度か描画処理を行ってしまう事態が起き、
パフォーマンスの低下につながる恐れがあります。それを解決するのが、requestAnimationFrameメソッドです。
<中略>
長所…毎回、ちゃんと描画できるタイミングで実行されるようになるため、「DOMやCSSを書き換えたが、描画できないタイミングだったので実際には動いていない」という無駄な処理が発生しない。
setintervalメソッドと同様に、ブラウザ上でタブが非アクティブな状態になると、処理が軽減される。
短所…処理が実行さえっるタイミングでブラウザの画面更新のタイミングに依存するため、狙ったときに実行できない。また、実行されるタイミングも必ずしも一定とは限らないため、決まった間隔で処理させたい場合には、setTimeoutメソッドやsetintervalを使用するといい。

ブレイクスルーjavascript70頁~71頁より

ここで質問がありなす。

①setTimeoutメソッドを利用すると、「指定した間隔」と「ブラウザが画面を更新するタイミング」に差が生じるとはどういうことなのでしょうか。更新すると何度が描画処理を行ってしまうとありますが、いまいち理解できません。

var canvas = document.getElementById("canvas");

//canvas要素から描画コンテキストの取得
var ctx = canvas.getContext("2d");

//16.6ミリ秒(0.01666...秒)
var interval = Math.floor(1000/60); 

var x=5;
var y=5;

function draw() {
    ctx.clearRect(0,0,500,500);

    x+=5;
    y+=5;

    //パスを初期化。ここでいうパスとは、領域の境界線のようなものだという。
    ctx.beginPath();

    //塗りつぶす色を指定。
    ctx.fillstyle = "#99ff66";//緑色

    //長方形のパスを作成。第一引数がx座標(横)、第二引数がy座標(縦)、第三引数が横幅、第四引数が高さだ。座標というのは、ブラウザの表示領域のX軸とY軸のことだ。0,0にすれば、ブラウザの左上に表示される。
    ctx.rect(x,y,100,200);//rectは、rectangel(長方形、矩形)の略である。

    //円形のパスを作成。
    //ctx.arc(100,100,40,0,Math.PI*2);//第一引数と第二引数が座標(横縦)、第三引数が半径、第四が円の始まりの角度、第五が円の終わりの角度、第六が描く向き 

    //fillで長方形を塗りつぶす。何も指定しないと黒色となる。
    ctx.fill();//fillは、容器や場所などをいっぱいにする、満たすという意味だ。

    //最後にbeginPathで開始したパスを閉じる。
    ctx.closePath();

    setTimeout(draw,interval);

}

draw();


上記がrequestAnimationFrameメソッド無しで、下記がrequestAnimationFrameメソッドありのソースです。同書籍の71頁に

<中略>ブラウザを開いて、動いていることを確認しましょう。先ほどと同じ動作となっていれば大丈夫です。

とありましたが、requestAnimationFrameありだと図形の描画の速さが無しに比べて早く、タスクマネージャーを開いてみてみると無しに比べ負担が増えたように見えます。これは、先ほどと同じ動作とは違うという認識であっておりますか。またどうすると先ほどと同じ動作になるのでしょうか。

var canvas = document.getElementById("canvas");

//canvas要素から描画コンテキストの取得
var ctx = canvas.getContext("2d");

//16.6ミリ秒(0.01666...秒)
var interval = Math.floor(1000/60); 

//よくわからないが、settimeoutで指定した間隔とブラウザが画面を更新する時機に差が生じ、それによって不都合なことがおきるのを防ぐのがrequestAnimationFrameという。
//しかし"ブラウザが画面を更新する時機"というのがどういうことなのかさっぱり理解できない。

window.requestAnimationFrame =
    window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(cb) {setTimeout(cb,17);};

var x=5;
var y=5;

function draw() {
    ctx.clearRect(0,0,500,500);

    x+=5;
    y+=5;

    //パスを初期化。ここでいうパスとは、領域の境界線のようなものだという。
    ctx.beginPath();

    //塗りつぶす色を指定。
    ctx.fillstyle = "#99ff66";//緑色

    //長方形のパスを作成。第一引数がx座標(横)、第二引数がy座標(縦)、第三引数が横幅、第四引数が高さだ。座標というのは、ブラウザの表示領域のX軸とY軸のことだ。0,0にすれば、ブラウザの左上に表示される。
    ctx.rect(x,y,100,200);//rectは、rectangel(長方形、矩形)の略である。

    //円形のパスを作成。
    //ctx.arc(100,100,40,0,Math.PI*2);//第一引数と第二引数が座標(横縦)、第三引数が半径、第四が円の始まりの角度、第五が円の終わりの角度、第六が描く向き 

    //fillで長方形を塗りつぶす。何も指定しないと黒色となる。
    ctx.fill();//fillは、容器や場所などをいっぱいにする、満たすという意味だ。

    //最後にbeginPathで開始したパスを閉じる。
    ctx.closePath();


    requestAnimationFrame(draw);


    setTimeout(draw,interval);
}

draw();


ブラウザがchrome61.0.3163.100でosがwindows10での環境での疑問です。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+6

描画のタイミング

テレビで流れる「動画」でも、実際には1秒に60コマの映像を送っている、というように、ブラウザでも表示は連続しているわけではなく、1秒間に何回か書き換えています。requestAnimationFrameは、その書換の直前のタイミングで実行されます。

時には更新回数が1秒に数回まで落ちることもあるので、固定時間でsetTimeoutしていると、1描画の間に何回も書き換えることなって、表示されず無駄です。

Google ChromeのDeveloper Toolsにある「Performance」を測定してみると、JavaScriptの実行や画面描画のタイミングが見えて、わかりやすいと思います。

コードの比較

これは、requestAnimationFrame版のコードがおかしいです。

  • 連続して描くのに、requestAnimationFrameの所要時間を計っていない
  • requestAnimationFramesetTimeoutを両方呼び出しているために、実行回数がねずみ算式に増えていく

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/20 19:25

    ご返答ありがとうございます。
    >>Chromeの次の画面更新までほぼ0〜16msの間で揺れます。
    ブラウザが行う何らかの変化があったときのみに発動する更新は、必ずしも1/60秒で更新されるわけではないけれども、
    settimeoutで「1/60秒」と指定するというのは、先述の更新が必ず1/60秒毎に発生する、という仮定のもとにある、ということですよね。
    ところで、"揺れる"というのは、どういうことなのでしょうか。0~1/60秒の間のどこかで描画を行う、ということでしょうか。

    >>しかし、次の描画タイミングが制御できないので、物体が1pxしか動かなかったり9pxも一気に飛んだりします。
    つまり、ブラウザが1/60秒のタイミングで更新するとして、5pxずつ動くけれども、5pxの次、つまり10px以後の描画タイミングが制御できない、ということでしょうか。
    重ね重ね申し訳ありませんが、一つ質問させてください。更新のタイミングが1/60秒で、描画もほぼほぼ同じですが、同じ1/60秒で描画されるはずの処理のタイミングが制御できない、ということですよね。
    settimeoutで1/60で描画するように設定しても何らかの理由で描画のタイミングがつかめなくなってしまうということでしょうか。

    キャンセル

  • 2017/11/20 20:23

    ChromeはPC全体の負荷を考慮に入れつつ、勝手に画面更新タイミングを1/60〜1/1秒の間で調整します。

    そして、setTimeoutやrequestAnimationFrameによって実行された関数は、
    この描画タイミング云々を考慮に入れて作られた関数ではありません。
    最初の関数実行時の時間を元に、「1秒掛けて300px動作か…現在の時刻は0.5秒経過しているから、150px先まで瞬間移動させればいいな」と判断しています。

    従って、このような流れになります
    500ms: 150px先へ移動よろしく → 15ms後に反映
    516ms: 155px先へ移動よろしく → 16ms後に反映
    532ms: 160px先へ移動よろしく → 0ms後に反映 ← この一瞬だけ10px移動したように見えたぞ?
    548ms: 165px先へ移動よろしく → 1ms後に反映

    このように、ユーザーの目には16ms毎に5pxずつ移動しているように見えてほしいのにフラフラ動いているように見えるわけです。
    ユーザーは1/60秒のごく短い時間を絶対的な位置ではなく前後の流れで判断します。
    等間隔で動く物体は速度が安定していると判断しますし、
    急に移動速度が上がったり、下がったりする様を見て、「このアニメーションしている物体は揺れている」と錯覚することでしょう。

    ではなぜrequesrAnimationFrameが安定しているかというと、描画完了直後に必ず関数が走るからです。
    500ms: 150px先へ移動よろしく → 14ms後に反映
    516ms: 155px先へ移動よろしく → 13ms後に反映
    532ms: 160px先へ移動よろしく → 15ms後に反映
    548ms: 165px先へ移動よろしく → 14ms後に反映

    重い時は重い時なりに、軽い時は軽い時なりに、次の描画タイミングが(遅い方に)安定します。
    従って、ユーザーの目には等速で移動し続けるように見えます。

    キャンセル

  • 2017/11/21 19:32

    ご返答ありがとうございました。ここまで付き合っていただいて感謝しております。

    キャンセル

+4

①setTimeoutメソッドを利用すると、「指定した間隔」と「ブラウザが画面を更新するタイミング」に差が生じるとはどういうことなのでしょうか。

setTimeoutメソッドによる処理のスケジューリングは「ブラウザが画面を更新するタイミング」とは無関係に行われます. つまり, 画面を更新する直前かもしれませんし, 直後かもしれません. 
一方requestAnimationFrameメソッドによる処理のスケジューリングは「ブラウザが画面を更新する直前」であることが保証されます. 

canvas要素でアニメーションを実現する場合, スクリーンが書き換わる前に一度だけグラフィックを書き換えれば良いので, requestAnimationFrameメソッドのほうが適していることになります.

NOTE:
とは言え, JavaScriptでは何れのメソッドを用いたとしても確実に指定した時刻に処理を行わせることは出来ません. あくまで「処理の実行を予約」するだけです. 直前に重い処理が挟まると容易に実行時刻はズレます. requestAnimationFrameメソッドでは次の次のフレーム更新時かもしれません.

コードにバグがあります

requestAnimationFrame(draw);
setTimeout(draw,interval);


上記コードにより処理のスケジューリングが倍々ゲームで増えています. 後者をコメントアウトして下さい. 

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

checkベストアンサー

+3

    requestAnimationFrame(draw);
    setTimeout(draw,interval);

最後の2行がおかしいです
約16ms毎に2回drawメソッドを叩き始めるので、1秒後には2^60回のdrawメソッドを呼びまくる計算になります。

そりゃ重くて落ちるわ。

setTimeout(draw,interval);の行を削除してもう一度確かめてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る