jQuery / メソッド / .html() - 要素の内容を取得・置換する

jQueryの.html()は、引数なしなら選択した最初の要素の内部HTML文字列を取得します。文字列を渡すと、外側の要素を残して選択した全要素の内容を置換します。ユーザー入力を文字として表示するなら、HTMLとして解釈する.html()ではなく.text()を使います。

取得・設定とtext・valの違い

.html()の仕様では取得結果は文字列、設定結果はjQueryオブジェクトです。内部HTMLはDOM(Document Object Model、文書オブジェクトモデル)をブラウザーが文字列化した値なので、元ファイルの空白やタグ表記と完全一致するとは限りません。

呼び出し 結果・動作 複数要素の場合
.html() 内部タグを含むHTML文字列 最初だけ取得
.html(markup) 内容をHTMLで置換 全てに適用
.text() タグを除いたテキスト 全てのテキストを連結
.text(value) 文字列をテキストで表示 全ての内容を置換
.val() フォームコントロールの現在値 取得は最初のコントロール

.text()は文字列をタグとして解釈しません。inputやtextareaの現在値は内部HTMLではないため.val()を使います。空の選択の.html()取得結果はundefinedです。続けて文字列メソッドを使う前に対象の存在を確認します。

HTML置換とテキスト表示の完全な例

全コードをUTF-8のhtml-demo.htmlとして保存し、ブラウザーで開きます。コンテンツ配信ネットワーク(CDN)から公式jQueryファイル4.0.0を読むインターネット接続が必要ですが、インストールは不要です。取得・置換・入力表示・初期化と以下のコンソール例を3.7.1と4.0.0で実行しました。

最初のボックスにはstrongのAlpha、次にはemのBetaがあります。結果も.text()で出すので、取得したHTMLは解釈されず文字列で見えます。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Read and replace HTML contents</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%; }
    .box { border: 2px solid teal; padding: 16px; margin: 12px 0; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <h1>HTML markup versus plain text</h1>
  <button type="button" id="read">Read</button>
  <button type="button" id="replace">Replace with fixed HTML</button>
  <label>Message <input id="message" value="&lt;b&gt;Hello&lt;/b&gt;"></label>
  <button type="button" id="plain">Show message as text</button>
  <button type="button" id="reset">Reset</button>
  <div id="examples">
    <div class="box"><strong>Alpha</strong></div>
    <div class="box"><em>Beta</em></div>
  </div>
  <p>Delegated clicks: <span id="clicks">0</span></p>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const $boxes = $(".box");
      const initial = $boxes.get().map(node => node.innerHTML);
      let clicks = 0;
      function report() {
        $("#result").text(`first html=${$boxes.html()}\ncombined text=${$boxes.text()}\nboxes=${$boxes.length}`);
      }
      $("#read").on("click", report);
      $("#replace").on("click", function () {
        $boxes.html('<button type="button" class="child">New</button>');
        report();
      });
      $("#plain").on("click", function () {
        $boxes.text($("#message").val());
        report();
      });
      $("#examples").on("click.htmlLesson", ".child", function () {
        clicks++;
        $("#clicks").text(clicks);
      });
      $("#reset").on("click", function () {
        $boxes.html(function (index) { return initial[index]; });
        $("#message").val("<b>Hello</b>");
        clicks = 0;
        $("#clicks").text(clicks);
        report();
      });
      report();
    });
  </script>
</body>
</html>

初期結果はHTMLがAlphaだけ、テキストが両方の連結です。

first html=<strong>Alpha</strong>
combined text=AlphaBeta
boxes=2
  1. Readで現在値を読み直します。first htmlには外側のdivは含みません。
  2. Replace with fixed HTMLで両方の内容がNewボタンになります。ボックス数は2のまま、combined textはNewNewです。
  3. 各NewボタンでDelegated clicksが増えます。置換を繰り返しても一度のクリックで一回だけです。
  4. Show message as textで<b>Hello</b>がタグの文字ごと両方に表示され、b要素はできません。
  5. Messageを編集して再実行します。.val()で読み、.text()に渡す経路です。
  6. Resetは各ボックスの保存済みHTML、入力値、クリック数0を戻します。外側の委任イベントは再登録しません。

信頼できない入力をHTMLとして挿入しない

HTMLを解釈するメソッドは通常のタグだけでなく、実行可能な属性やスクリプトも扱う可能性があります。不明な入力をそのまま.html()に渡すとXSS(Cross-Site Scripting、クロスサイトスクリプティング)の原因になります。例の置換は開発者が固定した文字列だけで、Messageはテキストとして処理します。

サーバーから受け取っただけでは安全とは限りません。コメント、検索語、外部データを文字として見せるなら.text()です。外部のリッチHTMLが必要なら、許可する要素・属性と実績ある無害化処理を別途設計します。単純な文字列置換や一部タグの削除だけを完全な対策と考えません。

置換した子のイベントとデータ

.html(newHtml)は追記せず既存内容を全て置換します。古い子ノードと、そのjQueryデータ・直接イベントは整理されます。開発者ツールのConsoleで次の完全な部分コードを実行します。一時コンテナーを作り、最後に整理します。

const $host = $('<div><button class="child">Old</button></div>').appendTo("body");
const $old = $host.find("button");
let direct = 0;
let delegated = 0;
$old.data("token", "stored").on("click", () => direct++);
$host.on("click", ".child", () => delegated++);
$host.html('<button class="child">New</button>');
$host.find("button").trigger("click");
console.log(`direct=${direct} | delegated=${delegated}`);
console.log(`old attached=${document.contains($old[0])} | old data=${typeof $old.data("token")}`);
$host.remove();
direct=0 | delegated=1
old attached=false | old data=undefined

新ボタンには以前の直接イベントがないのでdirectは0です。親は残り、.on()の委任イベントが新しい.childも処理するためdelegatedは1です。$oldの参照があっても文書にはなく、以前のtokenはundefinedです。親そのものを置換すれば、その親の委任イベントまで残るとは限りません。

関数型引数で要素ごとの内容を作る

.html(function(index, oldHtml))は各要素の0からのインデックスと以前のHTMLを受け、新文字列を返します。コールバックで以前の内容を使うならoldHtmlを参照します。次の例は固定したA・BのHTMLに番号spanを追加します。

const $boxes = $("<div><b>A</b></div><div><i>B</i></div>");
$boxes.html(function (index, oldHtml) {
  return oldHtml + "<span>" + (index + 1) + "</span>";
});
console.log($boxes.get().map(node => node.innerHTML).join(" | "));
console.log(`empty getter=${typeof $().html()}`);
$boxes.remove();
<b>A</b><span>1</span> | <i>B</i><span>2</span>
empty getter=undefined

oldHtmlが自動的に安全になるわけではありません。ここでは出所の定まった内容だけを使い、外部入力の連結へ拡張しません。変化しない場合はセレクターの範囲、対象の存在、内部構造、Networkの読み込み、Consoleのエラーを確認します。追記が目的なら、内部全体を置換する.html()が適切かも検討します。