フラグメント(DocumentFragment)での効率的な操作

概要

DocumentFragment は、実 DOM ツリー(画面に描画される DOM)とは切り離された、軽量なコンテナノード(nodeType === 11)です。
フラグメントの中で要素をまとめて組み立て、最後に親要素へ 一度に挿入 することで、レイアウト計算・再描画・スタイル計算(いわゆる reflow / repaint)を最小化 できます。
挿入時には「フラグメント自身」ではなく「その子要素たち」が親に移動するため、フラグメントは自動的に空 になります(使い捨ての作業台のイメージ)。


なぜ効率的か(パフォーマンスの要点)

  • バッチ挿入:大量のノードを1回の DOM 操作で投入できるため、逐次挿入に比べてレイアウトやスタイル計算の発生回数が減ります。

  • オフドキュメント作業:画面外(未接続)でノードを組み立てるため、その間はレイアウトが発生しません。

  • 移動が安い:フラグメントを挿入すると、子ノードが**移動(adopt)**されるだけでコピーされないためコストが低い。


基本 API と挙動

js
// 生成 const frag = document.createDocumentFragment(); // 子の追加(通常の Node と同様に扱える) frag.appendChild(document.createElement('li')); frag.append('text', document.createElement('div')); // append は文字列も可 // 親へ投入(子が移動し、frag は空になる) list.appendChild(frag); // または list.append(frag)
  • appendChild(frag)append(frag) を呼ぶと、frag の子だけが親に挿入され、frag は空になります。

  • querySelector / querySelectorAllDocumentFragment でも使用可能です。

  • ノードを移動する挙動なので、既存ノードに付けたイベントリスナやプロパティは保持されます(新規に HTML 文字列から作ったノードには、当然リスナは付いていません)。


代表的な使い方

1) 大量要素の一括追加

js
const ul = document.getElementById('items'); const frag = document.createDocumentFragment(); for (let i = 0; i < 10000; i++) { const li = document.createElement('li'); li.textContent = `Item ${i}`; frag.appendChild(li); } ul.appendChild(frag); // 一度の挿入で完了

2) 既存ノードの再配置/並べ替え

js
const container = document.getElementById('container'); const items = [...container.querySelectorAll('.item')]; // 例えばデータ属性でソート items.sort((a, b) => Number(a.dataset.order) - Number(b.dataset.order)); const frag = document.createDocumentFragment(); for (const el of items) frag.appendChild(el); // 移動(コピーではない) container.appendChild(frag); // 並び替えを一括反映

3) HTML 文字列 → フラグメント化して安全に挿入

Range#createContextualFragment を使うと、コンテキストに沿ったパースを行い、結果を DocumentFragment として得られます。

js
const tpl = ` <li class="entry"><strong>Title</strong><span class="meta">meta</span></li> <li class="entry"><strong>Title2</strong><span class="meta">meta2</span></li> `; const range = document.createRange(); range.selectNode(document.body); // パース基準となるコンテキスト const frag = range.createContextualFragment(tpl); document.getElementById('list').appendChild(frag);

innerHTML と同様に HTML をパースしますが、createContextualFragment は DOM ノードとして直接受け取れるため、挿入前にフラグメント内で検査・加工しやすい利点があります。挿入済みの <script> は通常自動実行されません。

4) <template> 要素と組み合わせる

<template>contentDocumentFragment です。複製してから中身を書き換え、一括挿入できます。

html
<template id="row-tpl"> <tr><td class="name"></td><td class="price"></td></tr> </template> <table><tbody id="tbody"></tbody></table>
js
const data = [{name:'Apple', price:120}, {name:'Orange', price:80}]; const tbody = document.getElementById('tbody'); const frag = document.createDocumentFragment(); const tpl = document.getElementById('row-tpl'); for (const item of data) { const clone = tpl.content.cloneNode(true); // 深い複製: fragment が返る clone.querySelector('.name').textContent = item.name; clone.querySelector('.price').textContent = item.price; frag.appendChild(clone); } tbody.appendChild(frag);

5) 置換系 API との併用(全入れ替えを一発で)

js
const panel = document.getElementById('panel'); const frag = document.createDocumentFragment(); // ... frag に新レイアウト構築 panel.replaceChildren(frag); // 既存子をすべて置換して一括反映

よくある落とし穴と注意点

  • 挿入後にフラグメントは空:挿入後もフラグメント内の要素にアクセスしたい場合は、挿入前に参照を保持しておくか、挿入先から再取得します。

  • イベント/状態の扱い:ノードをフラグメントへ「移動」する場合、ノードに付いているイベントリスナや一部の状態(例えば value など)は維持されます。新規生成のノードには当然リスナが無いので、必要なら挿入前に付与するか、イベント委任を使います。

  • 再描画の最小化 ≠ 変更通知が1回になるMutationObserver は追加された各ノードを検出します。とはいえ、レイアウト計算はまとめて行われやすいため実効パフォーマンスは向上します。

  • 外部ドキュメントからの取り込みDOMParser などで別 Document を作った場合は、document.importNode(node, true) で現在の文書へインポートしてからフラグメントに追加します。

  • アクセシビリティ:大量追加で DOM が急増すると、スクリーンリーダーが一度に多くのノードを認識します。段階的レンダリングや仮想スクロールも検討してください。


ベストプラクティス

  1. 作るのはオフライン、入れるのは一度:ループ内で親に直接追加せず、まずフラグメントに積む。

  2. テンプレートを活用:繰り返し UI は <template> + content.cloneNode(true) が読みやすくミスが減ります。

  3. イベント委任:大量要素に個別リスナを付けず、親にまとめて付けると軽量。

  4. 計測する:規模によってはブラウザの最適化で差が小さい場合もあるため、Performance API などで実測し、必要な箇所にだけ適用する。

  5. 置換 API を併用replaceChildren / before / after / replaceWith はフラグメント(の子)を受け取れるため、段組み・リスト全体の差し替えに有効。


まとめ

DocumentFragment は「オフラインで DOM を組み立て、最後に一気に実 DOM に反映」するための標準的なテクニックです。大量追加、並べ替え、テンプレート展開、HTML 文字列の安全な取り回しなどで効果を発揮します。
描画コスト・コードの見通し・保守性の観点からも、複数ノードの挿入が絡む場面ではまずフラグメントを検討するのが推奨です。

ChatGPT5 生成日:2025/09/11