LoginSignup
5
4

More than 1 year has passed since last update.

[JavaScript] 画面の再描画を待つ・画面を強制的に再描画する

Last updated at Posted at 2021-12-23

WEB ブラウザ上での JavaScript で時間のかかる計算処理をするとき、直前で DOM の内容を変更してもすぐに画面に反映されないことがあります。

(理由に関しては少しややこしいため、本記事では不説明。)

従来の対処法として setTimeout()delay0 にするというものがありますが、ブラウザや状況によって思い通りに動作しないことがあります。

本記事では他の方法を書きます。

1. 結論: requestAnimationFrame() を 2 回呼ぶ

const repaint = async () => {
    for (let i = 0; i < 2; i++) {
        await new Promise(resolve => requestAnimationFrame(resolve));
    }
};
await repaint(); // 直前の DOM 変更を画面に反映

2. 例

2.1. 時間のかかる計算処理をすると、直前の DOM 更新がすぐに画面に反映されない

(() => {

    const calculate = () => {
        for (let i = 0; i < 10000000; i++) {
            new Date();
        }
    };

    (() => {

        const body = document.body;

        body.insertAdjacentHTML('beforeend', '<p>calculating ...</p>'); // メモ: すぐに画面に反映されない

        calculate(); // 時間のかかる計算処理

        body.insertAdjacentHTML('beforeend', '<p>finished</p>');

    })();

})();

理想的には「calculating ...」が表示されてから calculate() の処理をして欲しいですが、実際には calculate() が終わってから「calculating ...」と「finished」が表示されます。

2.2. setTimeout() は動作しない可能性あり

(() => {

    const wait = (delay = 0) => new Promise(resolve => setTimeout(resolve, delay));

    const calculate = () => {
        for (let i = 0; i < 10000000; i++) {
            new Date();
        }
    };

    (async () => {

        const body = document.body;

        body.insertAdjacentHTML('beforeend', '<p>calculating ...</p>');

        await wait(); // setTimeout() により画面の再描画を促す ※動作しない可能性あり

        calculate(); // 時間のかかる計算処理

        body.insertAdjacentHTML('beforeend', '<p>finished</p>');

    })();

})();

delay: 0setTimeout() によって画面の再描画を促すことができますが、ブラウザや状況によっては動作しないことがあります。

delay500 等に増やすことによって改善する場合がありますが、それでも確実ではないようです。

参考「#4575011 - javascript - Why is setTimeout(fn, 0) sometimes useful? - Stack Overflow

2.3. requestAnimationFrame() を 2 回呼ぶ

(() => {

    const repaint = async () => {
        for (let i = 0; i < 2; i++) {
            await new Promise(resolve => requestAnimationFrame(resolve));
        }
    };

    const calculate = () => {
        for (let i = 0; i < 10000000; i++) {
            new Date();
        }
    };

    (async () => {

        const body = document.body;

        body.insertAdjacentHTML('beforeend', '<p>calculating ...</p>');

        await repaint(); // 画面を再描画して待つ

        calculate(); // 時間のかかる計算処理

        body.insertAdjacentHTML('beforeend', '<p>finished</p>');

    })();

})();

requestAnimationFrame() はもともとブラウザの画面の再描画直前に何か処理をする機能を持つため、(待機しながら) 2 つ連続呼ぶことで以下の順に処理されます:

  1. 描画前に何らかの処理をする (ここでは resolve() のみ)
  2. 画面を再描画する
  3. 描画前に何らかの処理をする (ここでは resolve() のみ)
  4. 画面を再描画しようとするが、「時間のかかる計算処理」が先に行われる

よって、確実に画面を再描画することができ、また、その再描画を待機することができます。

requestAnimationFrame() 1 つだけだと、何もしない場合と同様に (その直後で) 再描画されません。

参考「Window.requestAnimationFrame() - Web API | MDN

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4