【JavaScript】createDocumentFragment() を使った複数要素の効率的な追加方法

ループの中で要素を1つずつ appendChild すると、そのたびに画面の再計算(リフロー)が起きて非効率です。createDocumentFragment() を使うと、まとめて作ってから一度だけDOMに挿入でき、効率的になります。

この記事では、基本の使い方から、モダンな append() での複数追加、innerHTML との使い分けまで解説します。

この記事の結論:document.createDocumentFragment() に要素を append していき、最後に一度だけ parent.appendChild(fragment) で挿入します。DOMへの反映が1回で済むのでリフローを減らせます。モダンな append(...nodes) でも複数まとめて追加できます。
スポンサーリンク

createDocumentFragmentとは(なぜ速いか)

createDocumentFragment() は、軽量な一時コンテナ(DocumentFragment)を作ります。ここに要素を追加している間はDOMに反映されないため、リフローやレンダリングが発生しません。すべて追加し終えてから一度だけDOMに挿入することで、再計算の回数を1回に抑えられます

また、フラグメントをDOMに挿入すると中身だけが移動し、フラグメント自体は空になります(余計なラッパー要素が残りません)。

複数のリストアイテムをまとめて追加する

ループで作った要素をフラグメントに集め、最後に一度だけ <ul> へ追加します。テキストは textContent で安全に設定します。

JavaScript:リストをまとめて追加
const list = document.getElementById("myList");
const fragment = document.createDocumentFragment();

for (let i = 0; i < 5; i++) {
  const li = document.createElement("li");
  li.textContent = `Item ${i + 1}`;
  fragment.appendChild(li); // フラグメントに追加(DOMには反映されない)
}

list.appendChild(fragment); // ここで初めてDOMに1回だけ挿入

テキスト設定に innerHTML ではなく textContent を使う理由はtextContent・innerHTMLの使い分けで解説しています(XSS対策)。

異なる要素をまとめて追加する

見出し・段落・ボタンなど、種類の違う要素もまとめて追加できます。

JavaScript:異なる要素をまとめて
const container = document.getElementById("container");
const fragment = document.createDocumentFragment();

const header = document.createElement("h2");
header.textContent = "ようこそ";

const p = document.createElement("p");
p.textContent = "これは段落です。";

fragment.append(header, p); // append は複数まとめて渡せる
container.appendChild(fragment);

モダンな書き方:append()で複数追加

実は append()複数のノードや文字列をまとめて渡せるため、簡単なケースならフラグメントを使わずに一度に追加できます。

JavaScript:append(…nodes)
const list = document.getElementById("myList");

// 配列で作ってスプレッドでまとめて追加
const items = Array.from({ length: 5 }, (_, i) => {
  const li = document.createElement("li");
  li.textContent = `Item ${i + 1}`;
  return li;
});

list.append(...items); // 1回の操作でまとめて追加
今は差が小さい:近年のブラウザは複数の追加をうまくまとめて処理するため、createDocumentFragment の速度メリットは昔ほど大きくありません。ただし、要素をいったん組み立ててから一度に挿入するという考え方は今も有効で、ループ内で毎回DOMに直接 appendChild する書き方は避けるべきです。

実測デモ:本当に速くなる?

↓ ボタンを押すと、各3000要素の追加にかかった時間をその場で計測します(お使いのブラウザでの実測値です):

  • ① 1つずつ appendChild
  • ② フラグメントで一括:
  • ③ 1つずつ+毎回レイアウト読み取り:

多くのブラウザで、①と②の差はごくわずかのはずです。これは現在のブラウザが、JavaScriptの処理中は追加をまとめて一度に描画するためで、「今は差が小さい」と言われる理由です。

本当の敵は「ループ内でのレイアウト読み取り」:③のように、ループの中で offsetHeightgetBoundingClientRect() などレイアウト情報を読み取ると、その都度ブラウザが強制的に再計算(リフロー)し、一気に遅くなります(レイアウトスラッシング)。createDocumentFragment が最も効くのはこの状況です。「書き込み(DOM追加)と読み取り(レイアウト参照)をループ内で混ぜない」ことが、フラグメント以上に重要な原則です。

innerHTML / insertAdjacentHTML との使い分け

大量のHTMLを文字列でまとめて作るなら innerHTML も速いですが、既存の要素やイベントリスナーが消えるなどの注意があります。

  • createDocumentFragment / createElement:要素を1つずつ細かく制御でき、イベントリスナーも付けられる。安全(XSSになりにくい)
  • innerHTML:HTML文字列で一気に作れて手軽だが、既存の中身を置き換える+ユーザー入力を入れるとXSSの危険
  • insertAdjacentHTML:既存を消さずに位置を指定して挿入できる

要素の動的生成や挿入方法の使い分けはcreateElement・insertAdjacentHTML・FormDataの使い分け、生成した複数要素の操作はquerySelectorAllで取得した複数要素を操作する方法も参考になります。

よくある質問(FAQ)

QcreateDocumentFragment()はなぜパフォーマンスに良いのですか?
Aフラグメントへの操作はDOMに反映されないため、追加のたびにリフローが起きません。全要素を追加してから一度だけDOMに挿入することで、再計算(レンダリング)の回数を最小化できます。
QcreateDocumentFragment()の使い方は?
Aconst frag = document.createDocumentFragment() で作成し、frag.appendChild(要素)(または frag.append(...))で追加していき、最後に parent.appendChild(frag) で一括挿入します。
QinnerHTMLとcreateDocumentFragment()はどちらが速いですか?
AinnerHTML はHTML文字列で一気に作るため大量生成に向きますが、既存のDOMを置き換え、ユーザー入力ではXSSの危険があります。createDocumentFragment は細かい制御ができ、イベントリスナーも保持できます。用途で使い分けてください。
Q今でもcreateDocumentFragmentを使う必要はありますか?
Aモダンブラウザでは速度差は小さくなりましたが、「組み立ててから一度に挿入する」考え方は有効です。簡単なケースは append(...nodes) でも十分で、ループ内で毎回DOMへ直接追加するのは避けます。
QcreateDocumentFragmentが一番効くのはどんなとき?
Aループの中で offsetHeight などレイアウト情報を読み取りながら要素を追加している場合に最も効果があります。読み取りのたびに強制的なリフロー(レイアウトスラッシング)が起きるためです。まとめて作って一度に挿入すれば、これを避けられます。

まとめ

createDocumentFragment() は、要素を一時コンテナにまとめてから一度だけDOMに挿入することで、リフローの回数を抑える手法です。リストや異なる要素をまとめて追加するのに便利です。

モダンな append(...nodes) でも複数まとめて追加でき、近年は速度差も小さくなっています。ただしループ内で毎回DOMに直接追加するのは避け、まとめてから挿入する書き方を心がけましょう。