Web アプリケーションでは、ユーザーの操作に応じてHTML 要素を動的に追加・削除する場面が頻繁にあります。TODO リストへの項目追加、通知バナーの表示と自動消去、フォームフィールドの動的生成など、DOM の操作はフロントエンド開発の基本スキルです。
・createElement で要素を作成し appendChild / append で追加する方法
・insertBefore・before / after で任意の位置に挿入する方法
・insertAdjacentHTML で HTML 文字列を直接挿入する方法
・remove・removeChild で要素を削除する方法
・replaceWith・replaceChild で要素を置換する方法
・DocumentFragment で大量要素を効率的に追加する方法
・TODO リスト・通知バナー・無限スクロールの実務パターン
createElement と appendChild で要素を追加する(基本)
DOM に要素を追加する最も基本的な手順は、document.createElement で要素を作成し、appendChild で親要素の末尾に追加するという 2 ステップです。
// 1. 要素を作成
const li = document.createElement("li");
// 2. テキストや属性を設定
li.textContent = "新しい項目";
li.className = "list-item";
// 3. 親要素の末尾に追加
const ul = document.getElementById("myList");
ul.appendChild(li);
appendChild は追加した要素(Node)を戻り値として返します。追加後にさらに操作を続けたい場合に便利です。
複数の属性やイベントを持つ要素を作成する
const btn = document.createElement("button");
btn.textContent = "削除";
btn.className = "btn btn-danger";
btn.setAttribute("data-id", "42");
btn.addEventListener("click", () => {
btn.closest(".card").remove();
});
document.getElementById("toolbar").appendChild(btn);
createElement で作った要素は、DOM に追加するまで画面には表示されません。作成しただけではメモリ上にのみ存在する状態です。append と appendChild の違い
モダンブラウザでは append メソッドが使えます。appendChild との違いを正確に把握しておきましょう。
| 比較項目 | appendChild | append |
|---|---|---|
| 引数の型 | Node のみ | Node と文字列の両方 |
| 複数引数 | 不可(1 つのみ) | 可能(カンマ区切り) |
| 戻り値 | 追加した Node | undefined |
| ブラウザ対応 | IE6+ | IE 非対応 |
const container = document.getElementById("container");
// 文字列とノードを同時に追加できる
const strong = document.createElement("strong");
strong.textContent = "重要";
container.append("お知らせ:", strong, "な更新があります");
同様に、先頭に追加する prepend も使えます。
const ul = document.getElementById("myList");
const li = document.createElement("li");
li.textContent = "先頭に追加された項目";
// 最初の子要素として追加
ul.prepend(li);
append / prepend を使う方がコードが簡潔になります。IE 対応が必要な場合のみ appendChild を使いましょう。任意の位置に要素を挿入する
insertBefore(従来の方法)
parentNode.insertBefore(newNode, referenceNode) で、参照ノードの直前に要素を挿入します。
const ul = document.getElementById("myList");
const newLi = document.createElement("li");
newLi.textContent = "2番目に挿入";
// 2番目の子要素の前に挿入(=2番目の位置になる)
const secondItem = ul.children[1];
ul.insertBefore(newLi, secondItem);
// referenceNode が null の場合は末尾に追加される
ul.insertBefore(newLi, null); // appendChild と同じ動作
before / after(モダンな方法)
before / after メソッドは、対象の要素を基準にして直前・直後に挿入します。insertBefore より直感的に書けます。
const target = document.getElementById("item3");
// target の直前に挿入
const newBefore = document.createElement("li");
newBefore.textContent = "item3 の前に追加";
target.before(newBefore);
// target の直後に挿入
const newAfter = document.createElement("li");
newAfter.textContent = "item3 の後に追加";
target.after(newAfter);
// 文字列も渡せる(テキストノードとして挿入)
target.before("テキスト: ");
| メソッド | 挿入位置 | 特徴 |
|---|---|---|
appendChild(node) |
親要素の末尾 | 従来の標準メソッド |
append(...nodes) |
親要素の末尾 | 複数・文字列対応 |
prepend(...nodes) |
親要素の先頭 | 複数・文字列対応 |
before(...nodes) |
対象要素の直前 | 兄弟として挿入 |
after(...nodes) |
対象要素の直後 | 兄弟として挿入 |
insertBefore(new, ref) |
参照ノードの直前 | IE 対応、戻り値あり |
insertAdjacentHTML で HTML 文字列を挿入する
HTML の文字列をそのまま DOM に挿入したい場合は insertAdjacentHTML が便利です。createElement で要素を組み立てる手間を省けます。
element.insertAdjacentHTML(position, htmlString); // position: 挿入位置を指定する文字列 // htmlString: 挿入する HTML 文字列
position には以下の 4 つの値を指定できます。
<!-- "beforebegin" → ここ --> <div id="target"> <!-- "afterbegin" → ここ --> 既存のコンテンツ <!-- "beforeend" → ここ --> </div> <!-- "afterend" → ここ -->
const target = document.getElementById("target");
// 要素の内部の末尾に追加(最もよく使う)
target.insertAdjacentHTML("beforeend", '<p class="note">追加テキスト</p>');
// 要素の内部の先頭に追加
target.insertAdjacentHTML("afterbegin", "<h3>見出し</h3>");
// 要素の直前(兄弟として)に追加
target.insertAdjacentHTML("beforebegin", "<hr>");
// 要素の直後(兄弟として)に追加
target.insertAdjacentHTML("afterend", "<p>後続コンテンツ</p>");
textContent を使うか、入力値を必ずエスケープしてください。innerHTML との違い
| 比較項目 | insertAdjacentHTML | innerHTML |
|---|---|---|
| 動作 | 既存の子要素を保持して追加 | 既存の子要素をすべて破棄して置換 |
| イベントリスナー | 既存要素のリスナーは維持 | 既存要素のリスナーは失われる |
| 位置指定 | 4 種類の位置を選べる | 要素内部の置換のみ |
| パフォーマンス | 差分のみパース | 全体を再パース |
innerHTML += で要素を追加すると、既存の子要素がすべて再生成されるため、イベントリスナーが消えてしまう問題があります。要素の追加には insertAdjacentHTML を使う方が安全です。
const list = document.getElementById("myList");
// 既存の li にクリックイベントを設定
list.querySelector("li").addEventListener("click", handleClick);
// innerHTML += で追加 → 既存の li が再生成されリスナーが消える
list.innerHTML += "<li>新しい項目</li>"; // handleClick が失われる
const list = document.getElementById("myList");
list.querySelector("li").addEventListener("click", handleClick);
// insertAdjacentHTML なら既存の li のリスナーは維持される
list.insertAdjacentHTML("beforeend", "<li>新しい項目</li>");
要素を削除する
remove(モダンな方法)
要素自身を DOM から削除する最もシンプルな方法です。
const item = document.getElementById("removeTarget");
item.remove(); // 要素を DOM から削除
// 削除した要素は変数に残っているため再利用可能
console.log(item.textContent); // まだアクセスできる
document.body.append(item); // 別の場所に再追加も可能
removeChild(従来の方法)
親要素を経由して子要素を削除する方法です。IE に対応する必要がある場合はこちらを使います。
const item = document.getElementById("removeTarget");
// 親要素から子要素を削除
item.parentNode.removeChild(item);
// すべての子要素を削除する場合
const parent = document.getElementById("container");
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
すべての子要素を一括削除する方法の比較
| 方法 | コード | 特徴 |
|---|---|---|
innerHTML |
el.innerHTML = "" |
最も簡潔。リスナーは GC で回収 |
replaceChildren() |
el.replaceChildren() |
モダン。引数なしで全削除 |
while + removeChild |
while(el.firstChild) el.removeChild(el.firstChild) |
従来の方法。IE 対応 |
replaceChildren() が最も意図が明確です。引数を渡すと子要素の置換にもなります。el.replaceChildren(newChild1, newChild2) のように使えます。要素を置換する
replaceWith(モダンな方法)
const oldEl = document.getElementById("oldMessage");
const newEl = document.createElement("div");
newEl.textContent = "更新されたメッセージ";
newEl.className = "message updated";
// oldEl を newEl に置換
oldEl.replaceWith(newEl);
// 文字列でも置換可能
// oldEl.replaceWith("テキストに置換");
replaceChild(従来の方法)
const parent = document.getElementById("container");
const oldChild = parent.children[0];
const newChild = document.createElement("p");
newChild.textContent = "置換後の要素";
// 親要素から子要素を置換
parent.replaceChild(newChild, oldChild);
DocumentFragment で大量の要素を効率的に追加する
ループで大量の要素を追加する場合、1 つずつ DOM に追加すると追加のたびにレイアウト再計算(リフロー)が発生してパフォーマンスが低下します。DocumentFragment を使えば、メモリ上でまとめて組み立ててから一度に追加できます。
const ul = document.getElementById("myList");
const items = ["りんご", "みかん", "ぶどう", "いちご", "メロン"];
// 5回のリフローが発生
items.forEach(item => {
const li = document.createElement("li");
li.textContent = item;
ul.appendChild(li); // 毎回 DOM が更新される
});
const ul = document.getElementById("myList");
const items = ["りんご", "みかん", "ぶどう", "いちご", "メロン"];
// DocumentFragment にまとめる
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement("li");
li.textContent = item;
fragment.appendChild(li); // メモリ上に追加(リフローなし)
});
// 1回のリフローで一括追加
ul.appendChild(fragment);
appendChild を直接使っても体感差はありません。数十〜数百の要素を追加する場合に DocumentFragment の効果が出ます。詳しくは「createDocumentFragment() を使った複数要素の効率的な追加方法」を参照してください。実務でよく使うパターン
TODO リスト(追加と削除)
<div id="todo-app"> <input id="todoInput" type="text" placeholder="タスクを入力"> <button id="addBtn">追加</button> <ul id="todoList"></ul> </div>
const input = document.getElementById("todoInput");
const addBtn = document.getElementById("addBtn");
const todoList = document.getElementById("todoList");
addBtn.addEventListener("click", () => {
const text = input.value.trim();
if (!text) return;
const li = document.createElement("li");
li.textContent = text;
// 削除ボタンを追加
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "×";
deleteBtn.addEventListener("click", () => li.remove());
li.append(" ", deleteBtn);
todoList.appendChild(li);
input.value = "";
input.focus();
});
自動消去される通知バナー
function showNotification(message, duration = 3000) {
const notification = document.createElement("div");
notification.className = "notification";
notification.textContent = message;
document.body.insertAdjacentElement("afterbegin", notification);
// 指定時間後にフェードアウトして削除
setTimeout(() => {
notification.style.opacity = "0";
notification.addEventListener("transitionend", () => {
notification.remove();
});
}, duration);
}
// 使用例
showNotification("保存しました", 2000);
イベント委任で動的に追加した要素にイベントを設定する
動的に追加した要素には、あらかじめ addEventListener を設定できません。親要素にイベントを設定し、event.target で判定するイベント委任パターンが有効です。
const todoList = document.getElementById("todoList");
// 親要素に1つだけイベントリスナーを設定
todoList.addEventListener("click", (e) => {
// クリックされた要素が削除ボタンかどうか判定
if (e.target.matches(".delete-btn")) {
e.target.closest("li").remove();
}
});
// 後から追加された li 内の .delete-btn もイベントが動作する
function addTodo(text) {
todoList.insertAdjacentHTML(
"beforeend",
`<li>${text} <button class="delete-btn">×</button></li>`
);
}
要素の追加・削除メソッド一覧
| 操作 | モダン API | 従来 API |
|---|---|---|
| 末尾に追加 | parent.append(node) |
parent.appendChild(node) |
| 先頭に追加 | parent.prepend(node) |
parent.insertBefore(node, parent.firstChild) |
| 直前に挿入 | ref.before(node) |
parent.insertBefore(node, ref) |
| 直後に挿入 | ref.after(node) |
parent.insertBefore(node, ref.nextSibling) |
| 要素を削除 | node.remove() |
parent.removeChild(node) |
| 要素を置換 | old.replaceWith(new) |
parent.replaceChild(new, old) |
| 全子要素を削除 | parent.replaceChildren() |
parent.innerHTML = "" |
関連記事
- 子要素を取得・操作する方法 — children・firstElementChild・追加・削除・置換
- 兄弟要素を取得する方法 — nextElementSibling で隣接要素にアクセス
- getAttribute の使い方 — 属性値の取得・設定・削除
- createDocumentFragment() を使った複数要素の効率的な追加方法
- Cannot read properties of null の原因と解決方法 — DOM 取得時のよくあるエラー
- classList の使い方完全ガイド — クラス属性の操作に特化した API
よくある質問
append を推奨します。文字列や複数の要素をまとめて追加でき、コードが簡潔になります。prepend・before・after も同系統のモダン API です。innerHTML += は既存の子要素をすべて再パース・再生成するため、設定済みのイベントリスナーが消失します。また、ユーザー入力を含む場合は XSS のリスクもあります。要素の追加には insertAdjacentHTML や append を使いましょう。addEventListener は呼び出し時点で存在する要素にしか設定されません。後から追加された要素にも対応するには、親要素にリスナーを設定するイベント委任パターンを使います。parent.addEventListener("click", e => { if (e.target.matches(".btn")) { ... } })remove() は要素を DOM ツリーから切り離すだけです。変数で参照を保持していればメモリに残っており、document.body.append(el) で再追加も可能です。参照がなくなればガベージコレクションで回収されます。DocumentFragment を使って一度に追加するか、仮想スクロール(表示領域の要素だけを DOM に保持)の導入を検討してください。数千件以上のデータは、ライブラリ(react-window 等)の活用も有効です。まとめ
JavaScript で HTML 要素を動的に追加・削除する方法を整理しました。createElement + append が基本で、HTML 文字列を直接挿入したい場合は insertAdjacentHTML、要素の削除は remove() を使うのがモダンな書き方です。
実務ではinnerHTML += を避けること(リスナー消失・XSS リスク)と、動的要素にはイベント委任を使うことの 2 点が特に重要です。これらを押さえておけば、TODO リストや通知、動的フォームなど、DOM 操作を伴う機能を安全かつ効率的に実装できます。

