理想
スムーズな UI 操作
最適化方針
60 FPS(Frames per second:1秒間に画面を更新する回数)を目指す
調査
改善
DOM 操作に加えて重い処理をしている場合、表示の反映が遅れる。
DOM 操作をした後は直ちにレンダリング処理を行い、ブラウザに反映される方が良い。
API のキャッシュの localStorage への保存など遅延させても良い処理は非同期化させる。
setTimeout()
メソッドを使用指定ミリ秒が経過している間にメッセージとしてキューに追加して、擬似非同期化を実現できる。
// DOM操作
const div = document.querySelector("#target");
div.textContent = "こんにちは";
// 重い処理を非同期化
setTimeout(() => {
for (let i = 0; 0 < 1000; i++) {
console.log("check");
}
}, 0);
requestIdleCallback()
メソッドを使用ブラウザがアイドルになったタイミングでスクリプト処理を実行する。
setTimeout()
は指定ミリ秒が経過したら強制的にコールバック関数を実行する。
const taskQueue = [task1, task2, task3, task4];
const isDone = false;
const runTasks = ({ didTimeout, timeRemaining }) => {
// didTimeout:timeoutオプションで指定された時間よりも後にコールバック関数が呼び出された場合、true
// timeRemaining():アイドル中の処理として与えられた残り時間を返す
while (taskQueue.length && timeRemaining() > 0) {
const task = taskQueue.shift();
task.run();
}
if (taskQueue.length) {
requestIdleCallback(runTasks);
}
isDone = true;
};
const requestId = requestIdleCallback(
runTasks,
// 2秒経ってからコールバック関数が呼び出されたらタイムアウト扱いになる
// (コールバック関数が呼び出されない訳ではない)
{ timeout: 2000 }
);
// アイドル状態になる前にユーザが離脱して、コールバック関数が呼び出されない場合を想定
window.addEventListener("beforeunload", () => {
if (!isDone) {
runTaks();
}
});
// キャンセルしたい場合
cancelIdleCallback(requestId);
scroll や keydown, input などの短い時間に大量に発生するイベントや間隔の短いタイマー内で DOM 操作や重い処理をしている場合、、画面のちらつきな度を招いている。
まずは、高頻度に実行する必要があるか考える。
仕方がない場合、実行間隔を調整。
→ Lodash の throttle()
やdebounce()
関数を使う
throttle()
:高頻度で起こる処理を一定時間のうち実行を一度に抑える
debounce()
:高頻度で起こる処理が終了して一定時間が経つと一度だけ実行する
textarea.addEventListener(
"input",
throttle(() => {
// 連続してinputイベントが発生しても
// 100msの間に一度しか実行されない
}, 100)
);
window.addEventListener(
("scroll",
debounce(() => {
// scrollイベントが発生しなくなってから
// 200ms経つと一度だけ実行される
}))
);
どうしても行われければいけない重い処理でメインスレッドを占有している場合、メインスレッドとは別のワーカースレッドに処理を異常することでレンダリング処理などを円滑に実行できる。
ただし、ワーカースレッドでは利用可能なブラウザ API が制限されている。
window.addEventListener('message', event => {
if (event.origin !== 'https://example.com') {
return;
}
console.log(event.data);
});
const button = document.querySelector('button');
button.addEventListener('click', event => {
window.postMessage({
command: 'fibonacci',
number: 50
})
});
self.addEventListener('message', event => {
if (event.data.command === 'fibonacci') {
const result = fibonacci(event.data.number);
postMessage(result);
}
})
const fibonacci = (i) => {
// フィボナッチ数列を計算
}
調査
改善
グローバル変数などに DOM オブジェクトを参照したままでにしている場合、グローバル変数 = null
などで GC の回収対象にする。
調査
GC が高頻度で実行されると、perfomance パネルのヒープ領域のグラフのメモリがノコギリの歯のように推移している
改善
オブジェクトの生成と破棄を繰り返さないように、事前に生成したオブジェクトをプールすることで GC の対象から外す。このことをオブジェクトプールと呼ぶ。
import MemoryPool from "memoryPool";
const pool = new MemoryPool(Array);
// poolから格納したオブジェクト(配列)を取り出せる(空の場合は新しいオブジェクトを作成して返す)
const array = pool.allocate();
// 使わなくなったオブジェクト(配列)をプールに格納する
pool.free(array);
イベントリスナやタイマーは明示的に解放しないと実行されないハンドラとして残り続けるため、常に監視、実行しないといけないためメモリや CPU の負荷になる。
調査
performance パネルの Memory のチェックを入れた状態でイベントリスナの数の推移を確認
改善
タイマーは明示的に停止しないと実行され続ける。
→ clearInterval()
で停止する。
const timerId = setInterval(() => {
console.log("Interval Timer");
}, 1000);
// タイマーは破棄されない
// timerId = null;
clearInterval(timerId);
setInterval()
を削除setInterval()
で呼び出す処理が重くインターバルより時間がかかると、前回の処理が終了する前に次のタイマー処理が予約(キューに追加)されてしまう。
→ 代わりにsetTimeout()
を再帰的に実行することで、前回の処理が終わるまで次の処理がキューに追加されることがない。
const interval = () => {
setTimeout(() => {
console.log("Interval Timer");
interval();
}, 1000);
};
interval();
使わなくなった DOM のイベントリスナなどは明示的に削除しないと残ったままになる。
→ removeEventListener()
メソッドでリスナの解除をする
const listener = () => console.log("button is clicked");
button.addEventListener("click", listener);
button.removeEventListener("click", listener);
document.removeChild(button);
イベントリスナが一度しか実行されない場合、once オプションを追加する
document.addEventListener(
"click",
() => {
console.log("document is clicked");
},
{ once: true }
);
可逆圧縮
元のデータの配列をアルゴリズムによって短く表現する方法
対応している画像形式:PNG, GIF, WebP
メリット
元のデータにデコードできるため、データの劣化がされない
非可逆圧縮
人が劣化を感じにくい部分のデータを主に省略して、データ量を小さくする方法
対応している画像形式:JPEG, WebP
メリット
可逆圧縮に比べて高い圧縮率
デメリット
データの劣化が起こる
ラスタ(ビットマップ)
ピクセル一つ一つに対応する色情報から画像を表現する形式
メリット
データの記録方法として単純なことから、アプリケーションから扱うときのデコード処理などの負荷が小さい
デメリット
データが画像サイズに比例して大きくなりやすい
拡大・縮小したりすると、劣化する
ベクタ
点の座標とそれを結ぶ線などを数値データ化し、演算によって画像を再現(ラスタライズ)する形式
違うサイズで再利用されるロゴやアイコンに使いやすい
メリット
拡大・縮小してもぼやけたり、劣化しない
画像サイズにファイルサイズが左右されない(ラスタと比べるとファイルサイズが小さい)
デメリット
繊細な画像を表現するとファイルサイズが大きくなってしまう
ベースライン
画像データを一つのブロックに保持する標準的な形式
対応している形式:JPEG, GIF, PNG
プログレッシブ
低解像度から高解像度(オリジナル)の状態を複数ブロックに分割して保持する形式
ブラウザは画像データを低解像度の状態から高解像度の状態へレンダリングしていくため、最初は粗く、ロードが進むにつれ徐々に鮮明になっていく
ユーザー体験に良い
対応している形式:JPEG, GIF, PNG, WebP
JPEG
GIF
PNG
WebP
SVG
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 200 200">
<g>
<title>青い円グラフです</title>
<desc>hogehogeについての調査です</desc>
<circle cx="50" cy="50" r="50" fill="blue" />
</g>
</svg>