こんにちは!フロントエンド・アーキテクチャの世界へようこそ。
日々、Webサイトを作ったり、JavaScriptでアニメーションを動かしたりしていると、ふとこんな疑問やイライラにぶつかったことはありませんか?
「あれ、コード通りに動かしてるはずなのに、なんかカクカクする……」
「画面をちょっといじりたいだけなのに、なんでブラウザってこんなに重いの?」
大丈夫ですよ。そのモヤモヤ、あなたの書き方が悪いわけでも、PCの性能が低いわけでもありません。実は、ブラウザの裏側の仕組み(レンダリングの裏側)を知るだけで、この世界は劇的に変わるんです。
今回は、Webブラウザの「お買い物事情」に例えながら、DOM操作の闇を華麗に解決する「FastDOM(ファストドム)」という伝説的なアプローチについて、一緒におしゃべりするように紐解いていきましょう!
—
1. ブラウザが「カクカク」してしまう本当メンテの理由
まず、敵を知ることから始めましょう。Webブラウザが画面を表示する仕組み、通称「レンダリング・パイプライン」のお話です。
JavaScriptでDOM(画面のパーツ)をいじるとき、私たちは無意識にこんなことをやっています。
1. 「ねえ、今の画面の幅っていくつ?」と読み取る(Read)
2. その幅に合わせて、「じゃあ、この要素の大きさを変えよう!」と書き込む(Write)
3. 「じゃあ、高さはいくつ?」とまた読み取る(Read)
4. 「それなら、こっちを動かそう!」とまた書き込む(Write)
これを、1秒間に何十回も(アニメーションの最中なんかは60回も!)繰り返しているわけです。
身近な例え:おつかいの「往復ビンタ」
想像してみてください。あなたが母からおつかいを頼まれました。
- 母「牛乳の値段を見てきて!」(Read)
- あなた、スーパーへ走る(DOMの読み取り)
- あなた「150円だったよ!」
- 母「じゃあ卵を買ってきて!あ、値段も見て!」(Write & Read)
- あなた、またスーパーへ走る……
これ、めちゃくちゃ非効率ですよね? 足がパンパンになってしまいます。ブラウザも全く同じです。
JavaScriptから「読み取り(Read)」と「書き込み(Write)」を交互にバラバラのタイミングで指示されると、ブラウザは「えっ、ちょっと待って! また画面の設計図を書き直さなきゃいけないの!?」と、慌てて「スタイル計算(Recalculate Style)」と「レイアウト(Layout、通称リフロー)」という重労働を何度も何度も繰り返すことになります。
これが、画面がカクつく(Jankる)正体なんです。
—
2. 救世主「FastDOM」の設計思想:一度におつかいを済ませよう!
ここで登場するのが、「FastDOM」というライブラリ(およびその設計思想)です。
FastDOMがやっていることは、非常にシンプルかつ天才的です。それは、
- 「読み取り(Read)」のお仕事は、まとめて最初に全部やる!
- 「書き込み(Write)」のお仕事は、そのあとでまとめて全部やる!
という、「おつかいの効率化」です。
ブラウザの裏側で起きている奇跡
FastDOMを使うと、JavaScriptからどんなにバラバラに「これ読んで!」「これ書いて!」と指示を出されても、FastDOMくんが一度それらを自分のバックパック(キュー=待ち行列)に綺麗に整理してしまいます。
そして、ブラウザが一番機嫌よくお仕事できるタイミング(アニメーションのフレームの始まりなど、`requestAnimationFrame`のタイミング)を見計らって、
1. まず、全ての「読み取り」を一度に処理する(ブラウザはレイアウトを1回しか計算しない!)
2. 次に、全ての「書き込み」を一度に処理する(ブラウザは画面の描き直しを1回で済ませる!)
という、華麗なバッチ処理(一括処理)を行ってくれるのです。
—
3. 実コードで体感しよう!FastDOMの優しさ
百聞は一見にしかず。実際にFastDOMの考え方を取り入れたコードを見てみましょう。
(※今回はFastDOMのコンセプトがひと目でわかるように、バニラなJavaScriptでその仕組みを再現した「ミニFastDOM」のイメージで解説しますね)
従来の「カクカクしがち」な書き方
// 【アンチパターン】読みと書きが交互に発生してブラウザが悲鳴を上げる例
const box = document.getElementById(‘my-box’);
// 1回目の読み書き
const currentWidth = box.offsetWidth; // Read (DOMの読み取り)
box.style.width = (currentWidth + 10) + ‘px’; // Write (DOMの書き込み)
// 2回目の読み書き(ここでブラウザは強制的に再計算を走らせます!)
const currentHeight = box.offsetHeight; // Read
box.style.height = (currentHeight + 10) + ‘px’; // Write
この書き方だと、JSが動くたびにブラウザのレイアウトエンジンが汗水たらして計算し直します。
FastDOM的な思想を取り入れた美しい書き方
それでは、FastDOM(あるいはその思想)を使ったアプローチを見てみましょう。
/
- FastDOMの思想をシンプルなオブジェクトで再現した例です
/
const myFastDom = {
// 読み取り用のキュー
reads: [],
// 書き込み用のキュー
writes: [],
// 読み取りを予約する関数
read(fn) {
this.reads.push(fn);
this.scheduleFlush();
},
// 書き込みを予約する関数
write(fn) {
this.writes.push(fn);
this.scheduleFlush();
},
// 処理を実行するスケジュールを組む
scheduleFlush() {
if (this.isScheduled) return;
this.isScheduled = true;
// ブラウザの描画タイミングに合わせて一度に実行する
requestAnimationFrame(() => {
this.flush();
});
},
// 溜まったお仕事一括実行タイム!
flush() {
// 1. まず「読み取り」を全部片付ける
const readTasks = this.reads.slice();
this.reads = [];
readTasks.forEach(fn => fn());
// 2. 次に「書き込み」を全部片付ける
const writeTasks = this.writes.slice();
this.writes = [];
writeTasks.forEach(fn => fn());
// フラグを戻す
this.isScheduled = false;
}
};
// — 実際に使ってみましょう —
const box = document.getElementById(‘my-box’);
// FastDOMにおつかいを頼む(順不同で渡しても大丈夫!)
myFastDom.read(() => {
const w = box.offsetWidth; // まとめて読む準備
myFastDom.write(() => {
box.style.width = (w + 10) + ‘px’; // まとめて書く準備
});
});
myFastDom.read(() => {
const h = box.offsetHeight; // これも一緒に読む
myFastDom.write(() => {
box.style.height = (h + 10) + ‘px’; // これも一緒に書く
});
});
このコードを実行すると、ブラウザへの負荷(レイアウトスラッシング)が綺麗に抑えられ、驚くほど滑らかなアニメーションやDOM操作が実現できます。
—
まとめ:ブラウザへの「思いやり」が、最高のUXを生む
いかがでしたでしょうか?
FastDOMの設計思想は、一言で言うと「ブラウザに対する思いやり」です。
- 画面の情報を「読むとき」と「書くとき」をごちゃ混ぜにしない。
- 順番を整理して、ブラウザが一番処理しやすいタイミングで一気に渡してあげる。
Web制作の初学者のうちは、「動けばいいや」とDOMをその場でガシガシいじりがちですが、少しだけこの「読み書きの分離(バッチ処理)」を意識してあげるだけで、あなたの作るWebサイトは見違えるほど軽快で、ユーザーにとって心地よいものになります。
「あ、今自分、読み取りと書き込みを交互にごちゃ混ぜにしてないかな?」
そう立ち止まれるようになったら、あなたも立派なフロントエンド・アーキテクチャの仲間入りです!
明日からのコードに、ぜひこの「おつかい効率化」の視点を取り入れてみてくださいね。それでは、快適なWeb開発ライフを!

コメント