【JavaScript】既存の要素を移動させる方法|appendChild・insertBefore・after・before・cloneNode の使い分けと実践パターン解説

「ボタンを押すと要素が別の場所に移動する」「リストの項目を上下に並び替える」——こういった DOM 操作は、挿入メソッドを既存要素に使うだけで実現できます。

ポイントは、appendChildappend などの挿入メソッドは、既存の要素を渡すと自動的に元の場所から取り除いて移動するという動作をすることです。この記事では移動の仕組みと各メソッドの使い分け、実践的なパターンを体系的に解説します。

スポンサーリンク

「移動」の仕組み:挿入メソッドは既存要素を自動で取り除く

DOM の挿入メソッド(appendChild など)に既にページ上に存在する要素を渡すと、その要素は元の位置から自動的に取り除かれ、指定した場所に挿入されます。つまり、removeChild を呼ばなくても「切り取り&貼り付け」になるのがポイントです。

HTML(移動前の状態)
<div id="boxA">
  <p id="target">このpを移動させる</p>
</div>
<div id="boxB">
  <!-- ここに target を移動させたい -->
</div>
既存要素を appendChild で移動する
const target = document.getElementById('target');
const boxB   = document.getElementById('boxB');

// appendChild に既存要素を渡すと「移動」になる
boxB.appendChild(target);

// 結果: #target は #boxA から消え、#boxB の末尾に追加される
// → removeChild は不要!
なぜ移動になるのか:DOM の仕様では、1つのノードはドキュメント内の1箇所にしか存在できません。そのため既存ノードを挿入しようとすると、ブラウザは自動的に元の親から取り除いてから挿入します。この動作はすべての挿入メソッドに共通です。

挿入メソッドの種類と使い分け

「どこに挿入するか」によって使うメソッドが変わります。それぞれの違いを整理します。

メソッド 挿入位置 複数要素 テキスト直接指定
parent.appendChild(el) 親の末尾 ×(1つずつ) ×
parent.prepend(el) 親の先頭
parent.append(el) 親の末尾
parent.insertBefore(el, ref) ref の直前 × ×
ref.before(el) ref の直前
ref.after(el) ref の直後
ref.replaceWith(el) ref と置換
各メソッドの使い方
const list  = document.getElementById('list');
const itemA = document.getElementById('itemA');
const itemB = document.getElementById('itemB');

// 末尾に移動
list.appendChild(itemA);
list.append(itemA);      // 同等(append は複数・文字列も渡せる)

// 先頭に移動
list.prepend(itemA);

// itemB の直前に移動
list.insertBefore(itemA, itemB);
itemB.before(itemA);     // 同等

// itemB の直後に移動
itemB.after(itemA);

// itemB と置き換え(itemB は消える)
itemB.replaceWith(itemA);

コピーしたい場合は cloneNode を使う

「元の位置に残しつつ、別の場所にも同じ要素を置く」場合は cloneNode でコピーしてから挿入します。

cloneNode でコピー
const original = document.getElementById('template');

// true を渡すと子孫要素も含めてディープコピー
const clone = original.cloneNode(true);

// clone を別の場所に挿入(original はそのまま残る)
document.getElementById('container').appendChild(clone);

// 注意:clone には id が重複する可能性があるため、
// clone した後に id を変更するか、最初から id を付けない設計にする
clone.id = 'template-copy';
cloneNode(true) のイベントリスナーはコピーされない:cloneNode は要素の HTML 構造と属性をコピーしますが、addEventListener で登録したイベントリスナーはコピーされません。クローン後に改めてイベントを登録する必要があります。(onclick 属性のようなインラインハンドラはコピーされます。)

実践:リストの項目を上下に並び替える

「↑」「↓」ボタンでリスト項目を1つずつ移動させる、よく使うUIパターンです。

HTML(並び替えリスト)
<ul id="sortableList">
  <li>項目 A <button class="move-up">↑</button><button class="move-down">↓</button></li>
  <li>項目 B <button class="move-up">↑</button><button class="move-down">↓</button></li>
  <li>項目 C <button class="move-up">↑</button><button class="move-down">↓</button></li>
  <li>項目 D <button class="move-up">↑</button><button class="move-down">↓</button></li>
</ul>
上下に並び替える実装
const list = document.getElementById('sortableList');

// イベント委譲でボタンのクリックを一括処理
list.addEventListener('click', (e) => {
  const btn = e.target.closest('.move-up, .move-down');
  if (!btn) return;

  const li   = btn.closest('li');
  const isUp = btn.classList.contains('move-up');

  if (isUp) {
    const prev = li.previousElementSibling;
    if (prev) list.insertBefore(li, prev); // li を prev の直前に移動
  } else {
    const next = li.nextElementSibling;
    if (next) next.after(li); // next の直後に移動
  }
});
先頭・末尾の判定は previousElementSibling / nextElementSibling で:previousElementSiblingnull なら先頭なので上移動できません。nextElementSiblingnull なら末尾なので下移動できません。境界チェックを入れることでリストの外に飛び出すバグを防げます。

実践:ドラッグ&ドロップでリストを並び替える

HTML5 の Drag and Drop API を使うと、マウスでドラッグして順番を変えるUIを実装できます。

HTML(draggable リスト)
<ul id="dndList">
  <li draggable="true" data-id="1">項目 1</li>
  <li draggable="true" data-id="2">項目 2</li>
  <li draggable="true" data-id="3">項目 3</li>
  <li draggable="true" data-id="4">項目 4</li>
</ul>
CSS(ドラッグ中のスタイル)
#dndList li {
  padding: 10px 16px;
  margin: 4px 0;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 6px;
  cursor: grab;
  transition: opacity 0.2s, background 0.2s;
}

#dndList li.dragging {
  opacity: 0.4;
  cursor: grabbing;
}

#dndList li.drag-over {
  background: #eff6ff;
  border-color: #93c5fd;
}
Drag and Drop による並び替え実装
const dndList = document.getElementById('dndList');
let draggingEl = null;

dndList.addEventListener('dragstart', (e) => {
  draggingEl = e.target.closest('li');
  draggingEl.classList.add('dragging');
  e.dataTransfer.effectAllowed = 'move';
});

dndList.addEventListener('dragend', () => {
  draggingEl?.classList.remove('dragging');
  dndList.querySelectorAll('.drag-over').forEach((el) => {
    el.classList.remove('drag-over');
  });
  draggingEl = null;
});

dndList.addEventListener('dragover', (e) => {
  e.preventDefault(); // ドロップを許可
  const target = e.target.closest('li');
  if (!target || target === draggingEl) return;

  // ドラッグ中の要素がターゲットの上半分にあるか下半分にあるかで挿入位置を決める
  const rect   = target.getBoundingClientRect();
  const midY   = rect.top + rect.height / 2;
  const isAbove = e.clientY < midY;

  dndList.querySelectorAll('.drag-over').forEach((el) => el.classList.remove('drag-over'));
  target.classList.add('drag-over');

  if (isAbove) {
    dndList.insertBefore(draggingEl, target);
  } else {
    target.after(draggingEl);
  }
});
dragover で e.preventDefault() が必須:デフォルトでは多くの要素はドロップを受け付けません。dragover イベントで e.preventDefault() を呼ぶことでドロップ可能になります。これを忘れると drop イベントが発火しません。

DocumentFragment で複数要素を一括移動する

複数の要素を移動するとき、1つずつ挿入するとその都度 DOM が更新されてパフォーマンスが落ちます。DocumentFragment にまとめてから1回の挿入で済ませると、リフローが1回に抑えられます。

DocumentFragment による一括移動
const source      = document.getElementById('source');
const destination = document.getElementById('destination');

// 移動したい要素を選択
const itemsToMove = source.querySelectorAll('.move-target');

// DocumentFragment に一時的に収める
const fragment = document.createDocumentFragment();
itemsToMove.forEach((el) => fragment.appendChild(el)); // source から切り離される

// 1回の操作で destination に挿入(リフローは1回)
destination.appendChild(fragment);

// ※ fragment 自体は DOM に追加されず、中身だけが展開される
DocumentFragment は DOM に追加されない:DocumentFragment は軽量なコンテナで、appendChild などで別の要素に挿入すると中身だけが展開されフラグメント自体は残りません。レンダリングに影響を与えずに複数ノードをまとめて操作できる点が最大のメリットです。

insertAdjacentElement との違い

insertAdjacentElement(position, el) はより細かい挿入位置を文字列で指定できるメソッドです。

insertAdjacentElement の挿入位置
const ref = document.getElementById('reference');
const el  = document.getElementById('moving');

// ref の開始タグの直前(ref の前の兄弟になる)
ref.insertAdjacentElement('beforebegin', el);

// ref の開始タグの直後(ref の最初の子になる)
ref.insertAdjacentElement('afterbegin', el);

// ref の終了タグの直前(ref の最後の子になる)
ref.insertAdjacentElement('beforeend', el);

// ref の終了タグの直後(ref の後の兄弟になる)
ref.insertAdjacentElement('afterend', el);

// before/after/prepend/append で同等の操作が可能
ref.before(el);        // beforebegin と同じ
ref.prepend(el);       // afterbegin と同じ
ref.append(el);        // beforeend と同じ
ref.after(el);         // afterend と同じ
before/after/prepend/append と insertAdjacentElement の使い分け:現代のブラウザでは beforeafterprependappend が使えるため、insertAdjacentElement は必須ではありません。ただし位置を文字列で動的に切り替えたい場合は insertAdjacentElement が便利です。

よくある質問

QappendChild と append の違いは何ですか?
AappendChild は1つのノードしか受け取れず、テキスト文字列も渡せません。append は複数のノードや文字列を同時に渡せる新しいメソッドです。どちらも既存要素を渡すと「移動」になる動作は同じです。特別な理由がなければ append を使うほうが柔軟です。
Q要素を「元に戻す」ボタンを実装するにはどうすればいいですか?
A移動前に元の親要素と次の兄弟要素(nextSibling)を変数に保存しておきます。「元に戻す」ときは originalParent.insertBefore(el, originalNextSibling) で元の位置に戻せます。nextSiblingnull の場合は末尾だったということなので、appendChild で末尾に追加します。
Qアニメーションと組み合わせて要素を「飛んでいく」ように移動させるには?
AgetBoundingClientRect() で移動前後の座標を取得し、CSS の transform: translate() で差分をアニメーションする「FLIP テクニック」が効果的です。要素を実際に移動させてから、逆方向にトランジションすることで自然な動きを実現できます。
QReact や Vue では DOM を直接操作して要素を移動させるべきですか?
AReact・Vue では DOM を直接操作せず、状態(配列の並び順など)を変更することで再レンダリングさせるのが基本です。直接 DOM を操作すると仮想 DOM との不整合が起きる可能性があります。React なら配列の sortsplice で並び替え、Vue なら reactive な配列を操作してください。
QcloneNode で複製した要素の id が重複してしまいます。どうすればいいですか?
Aid はページ内で一意である必要があります。クローン後に clone.id = "" で id を削除するか、clone.id = "new-unique-id" で別の id を付け直してください。querySelectorAll("[id]") でクローン内の全 id 付き要素を取得して一括でリセットする方法もあります。

まとめ

JavaScript での要素移動のポイントをまとめます。

  • appendChildappendbeforeafter などの挿入メソッドは、既存要素を渡すと自動的に移動になる
  • removeChild を呼ばなくても「切り取り&貼り付け」ができる
  • コピーしたい場合は cloneNode(true) → 挿入の2ステップで行う
  • 複数要素を一括移動するときは DocumentFragment でリフローを1回に抑える
  • ドラッグ&ドロップ実装は dragoverpreventDefault() を忘れない

要素の動的な追加・削除については【JavaScript】マウスオーバーで要素を動的に追加・削除する実践ガイド、クラスの操作は【JavaScript】classList の使い方完全ガイドもあわせてご覧ください。