jQuery / メソッド / .detach() - 要素を文書から取り外して再利用する

jQueryの.detach()は、選択した要素をDOM(Document Object Model、文書オブジェクトモデル)から取り外し、関連するjQueryのデータとイベントを保持します。再挿入するには、返されたjQueryオブジェクトまたは既存の参照を変数に残します。ブラウザーの保存領域やサーバーへの永続保存ではありません。

detach・remove・emptyの選び方

.detach()は同じ要素を一時的に外し、後で再利用するときに適しています。要素とその子が一緒に文書から外れますが、変数でノードを参照していれば再挿入できます。CSSで隠す場合と異なり文書内に存在しないため、文書を改めて検索しても見つかりません。

メソッド 文書から外れる対象 外した部分のjQueryデータ・イベント 主な用途
.detach() 選択要素と内容 保持 一時的な取り外しと再利用
.remove() 選択要素と内容 消去 不要な要素の破棄
.empty() 選択要素の子とテキスト 削除する子の分を消去 コンテナーを残して中を空にする

.remove()も、JavaScriptの変数が参照しているノードオブジェクト自体を必ず即座に消すわけではありません。再挿入できても、消去されたjQueryデータやイベントは自動復旧しません。.empty()はコンテナー自体を取り外しません。画面から消えるという見た目だけで選ばないようにします。

入力値とイベントを保持する完全な例

全コードをUTF-8のdetach-demo.htmlとして保存し、ブラウザーで開きます。コンテンツ配信ネットワーク(CDN)から公式jQueryファイル4.0.0を読み込むインターネット接続が必要ですが、パッケージのインストールは不要です。入力値、同一ノードと位置の復元、繰り返しの取り外し・初期化、以下のコンソール比較を3.7.1と4.0.0で実行しました。

カードの前にコメントノードを位置の目印として置きます。カードを外しても目印は残るので、Restoreで元の場所に戻せます。カードは.data()にcountを持ち、子ボタンには直接イベントを登録しています。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Detach and restore an editable card</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 24px auto; padding: 16px; }
    button, input { font: inherit; margin: 4px; max-width: 100%; }
    .card { border: 2px solid teal; padding: 16px; margin: 12px 0; }
  </style>
</head>
<body>
  <h1>Detach and restore a card</h1>
  <button type="button" id="detach">Detach</button>
  <button type="button" id="restore">Restore</button>
  <button type="button" id="reset">Reset</button>
  <div id="slot">
    <p>Before the card</p>
    <p id="after">After the card</p>
  </div>
  <p id="result" aria-live="polite"></p>
  <script>
    $(function () {
      const anchor = document.createComment("card position");
      $("#after").before(anchor);
      let $saved = null;
      function makeCard() {
        const $card = $('<section class="card"><label>Note <input value="Keep me"></label><p>Count: <span class="count">0</span></p><button type="button" class="increment">Increment</button></section>');
        $card.data("count", 0);
        $card.find(".increment").on("click", function () {
          const count = $card.data("count") + 1;
          $card.data("count", count).find(".count").text(count);
          report();
        });
        return $card;
      }
      let $card = makeCard().insertAfter(anchor);
      function report() {
        const attached = document.contains($card[0]);
        $("#result").text(`attached=${attached} | saved=${$saved !== null} | count=${$card.data("count")}`);
      }
      $("#detach").on("click", function () {
        if ($saved !== null) return;
        $saved = $card.detach();
        report();
      });
      $("#restore").on("click", function () {
        if ($saved === null) return;
        $saved.insertAfter(anchor);
        $saved = null;
        report();
      });
      $("#reset").on("click", function () {
        $card.remove();
        $saved = null;
        $card = makeCard().insertAfter(anchor);
        report();
      });
      report();
    });
  </script>
</body>
</html>

初期状態ではカードが文書内にあり、別途取り外して保持している状態ではありません。

attached=true | saved=false | count=0
  1. NoteをEdited noteに変え、カードのIncrementを一度押します。Countは1です。
  2. Detachでカードが消え、attached=false | saved=true | count=1になります。データは残りますが、文書内の.cardを改めて検索すると0件です。
  3. RestoreでBeforeとAfterの段落の間に戻ります。入力値はEdited note、Countは1のままです。
  4. もう一度Incrementを押すとCountが2になり、子のイベントも保持されていると分かります。
  5. DetachやRestoreを連続で押しても、保持状態を確認するコードによりカードを失ったり複製したりしません。
  6. 取り外し中もResetが使えます。古いカードを.remove()で整理し、入力値Keep meとCount 0の新しいカードを作ります。

同じノードの現在の入力値を保つことと、新しいHTML文字列からノードを作ることは異なります。Restoreは保持したノードを戻し、Resetは意図的に新しいノードを作ります。各新規カードにはイベントを一度だけ登録するため、Resetを繰り返しても一度のクリックで複数回増えません。

removeと比較してデータ・イベントを確認する

開発者ツールのConsoleで次の完全な部分コードを実行します。一時コンテナー内にボタンを二つ作り、一方をdetach、他方をremoveしてから両方を戻します。最後の行は実習用コンテナーを整理します。

const $host = $("<div>").appendTo("body");
let keptClicks = 0;
let removedClicks = 0;
const $keep = $("<button>Keep</button>").data("token", "kept")
  .on("click", () => keptClicks++).appendTo($host);
const $gone = $("<button>Remove</button>").data("token", "gone")
  .on("click", () => removedClicks++).appendTo($host);
const originalNode = $keep[0];
const $saved = $keep.detach();
console.log(`detached=${!document.contains($saved[0])} | data=${$saved.data("token")}`);
$saved.appendTo($host).trigger("click");
const $removed = $gone.remove();
$removed.appendTo($host).trigger("click");
console.log(`same node=${$saved[0] === originalNode} | kept clicks=${keptClicks}`);
console.log(`removed data=${typeof $removed.data("token")} | removed clicks=${removedClicks}`);
$host.remove();
detached=true | data=kept
same node=true | kept clicks=1
removed data=undefined | removed clicks=0

detachしたボタンは同一ノードで、tokenとクリックイベントが残り、kept clicksは1です。removeしたボタンも参照があるので挿入できますが、jQueryデータはundefined、直接イベントも消去されているためremoved clicksは0です。これはjQueryが管理するデータとイベントの比較です。外部タイマーや別ライブラリーの状態まで解除されるとは限りません。

セレクターによる絞り込みと復元時の注意

.detach(selector)のセレクターは、既に選んだ要素集合から取り外す要素を絞ります。その内部で子を新たに検索する引数ではありません。次のコンソール例はAとBを選び、.takeのBだけを外すため、コンテナーにはAが残ります。最後の二行は参照中のノードと一時コンテナーを整理します。

const $list = $('<div><p class="keep">A</p><p class="take">B</p></div>').appendTo("body");
const $items = $list.children("p");
$items.detach(".take");
console.log(`remaining=${$list.children("p").text()}`);
$items.remove();
$list.remove();
remaining=A

取り外した後に$(".card")で検索し直しても戻せません。保持した参照を使います。元の位置が必要ならコメントの目印などで別途管理します。.appendTo()は挿入先の末尾に入れるため、以前の位置は覚えていません。挿入先が一つなら既存ノードを移動し、複数ならコピーができる場合があるので対象数も確認します。

親に.on()で委任したイベントは、その親へのイベント伝播を利用します。別のコンテナーに移動した要素が、前の親の委任イベントまで持ち運ぶわけではありません。この例は保持を確認しやすいよう、子ボタンに直接イベントを登録しています。

戻らない場合は変数の上書き、目印や挿入先がまだ文書にあるかを調べます。変数を長期間保持すると取り外したオブジェクトも参照され続けます。不要になったら.remove()でjQueryの状態を整理し、参照も解除します。関連タイマーや外部購読は各APIで別途解除します。