JavaScript | DOM 操作:テキスト・内容変更 – innerHTML

JavaScript JavaScript
スポンサーリンク

innerHTML とは何か

innerHTML は、要素の「中身(子要素やテキスト)」を、HTML文字列として読み書きするためのプロパティです。文字列を代入すると、その場でパースされて要素やテキストに展開されます。ここが重要です:innerHTML は強力ですが“HTMLを実行する”ため、扱い方を誤るとセキュリティ(XSS)や保守性の問題を招きます。外部データをそのまま渡すのは絶対に避け、信頼できるテンプレートだけに限定しましょう。


基本の使い方(HTMLを差し込む・読む)

差し込みの例

<div id="box"></div>
<script>
  const box = document.getElementById("box");
  box.innerHTML = "<h2>タイトル</h2><p>説明文です。</p>";
</script>
HTML

この例では、文字列がパースされて h2 と p が生成され、要素内に展開されます。HTMLタグが“解釈される”点が textContent と決定的に異なります。

読み取りの例

<div id="card"><strong>重要</strong>なお知らせ</div>
<script>
  const card = document.getElementById("card");
  console.log(card.innerHTML); // "<strong>重要</strong>なお知らせ"
</script>
HTML

innerHTML を読むと、現在の要素内の“HTML文字列”がそのまま返ります。構造を文字列として扱いたいときに便利ですが、解析には DOM API を使うほうが安全で確実です。


textContent・innerText との使い分け(安全性と目的の違い)

textContent は「テキストだけ」を入れる・読むための安全な手段で、タグは解釈されません。ユーザー入力や外部データの表示は原則 textContent を使います。innerText は「画面に見えている文字」を基準に読み書きしますが、レイアウト計算が走ることがあり、頻繁な取得には不向きです。ここが重要です:HTMLを本当に展開したい時だけ innerHTML。それ以外は textContent をデフォルトにするのが堅実です。


重要ポイントの深掘り(XSS・イベント消失・性能)

XSS の危険性

外部から受け取った文字列を innerHTML に渡すと、悪意あるスクリプトやタグがそのまま実行・展開される可能性があります。たとえばユーザー名に <script>...</script> が紛れていた場合、ページで実行されてしまいます。ここが重要です:外部データは必ず“文字として”扱う(textContent)。どうしても HTML を差す必要がある場合は、厳密に検証済みのテンプレートだけを使用し、動的部分はエスケープした文字列を差し込みます。

イベントが消える問題

innerHTML で要素の中身を置き換えると、既存の子要素は丸ごと破棄されます。そこに紐付けたイベントリスナも失われます。ここが重要です:部分更新はできるだけ要素を差し替えず、必要箇所だけを DOM API(createElement、append、classList、textContent)で更新すると、リスナの再登録が不要で安定します。

性能と再描画

大きな HTML を innerHTML で何度も差し替えると、毎回パースと再描画が走り、体感が重くなります。ここが重要です:大量追加は DocumentFragment で組み立てて一括挿入、微小更新は DOM を直接操作し、文字列の全置換は避けるのが効率的です。


実践的な安全なパターン(テンプレート+文字差し込み)

構造は固定、可変部分は文字で

<article class="card">
  <h2><span id="title"></span></h2>
  <p id="desc"></p>
</article>
<script>
  const title = document.getElementById("title");
  const desc  = document.getElementById("desc");
  const data = { title: "ようこそ <b>安全</b>", desc: "タグは文字として扱う" };

  title.textContent = data.title; // タグは文字として表示
  desc.textContent  = data.desc;
</script>
HTML

構造をあらかじめ HTML で用意し、可変部分だけ textContent で差し込みます。見た目の装飾が必要なら、CSS や固定のタグで表現し、動的テキストは常に“文字”で扱います。

テンプレート要素を使う

<template id="row-tpl">
  <tr>
    <td class="name"></td>
    <td class="qty"></td>
  </tr>
</template>
<table><tbody id="tbody"></tbody></table>
<script>
  const tpl = document.getElementById("row-tpl");
  const tbody = document.getElementById("tbody");
  const rows = [{name: "Apple", qty: 3}, {name: "Banana", qty: 0}];

  const frag = document.createDocumentFragment();
  rows.forEach(r => {
    const node = tpl.content.cloneNode(true);
    node.querySelector(".name").textContent = r.name;
    node.querySelector(".qty").textContent  = String(r.qty);
    frag.append(node);
  });
  tbody.append(frag);
</script>
HTML

template の中は実行されず、クローンしてから安全に textContent で埋めることで、XSS を避けつつ柔軟なレンダリングができます。


innerHTML を使う“ありうる場面”と注意点

実運用で innerHTML を使うのは、信頼できる静的テンプレートを一括で展開したい、あるいは小規模な装飾をシンプルに差し込みたい場面に限ります。注意点として、イベントが消えること、スクリプトタグは通常実行されないこと(再挿入でも動かない)、パースコストがかかることを理解しておいてください。ここが重要です:差し替え前後で必要な状態(スクロール位置、フォーカス、イベント)を維持する工夫が必要で、可能なら DOM の粒度で更新するほうが安全で高速です。


実践例:限定的な装飾の差し込みと安全な更新

<p id="notice"></p>
<script>
  const notice = document.getElementById("notice");

  // 固定の小さなテンプレート:太字とリンクのみ(動的文字はエスケープして挿入)
  function escapeHTML(s) {
    return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);
  }

  function showNotice(userName) {
    const safeName = escapeHTML(userName);
    notice.innerHTML = `ようこそ、<strong>${safeName}</strong> さん! <a href="/help">ヘルプ</a>`;
  }

  showNotice('田中<script>alert(1)</script>'); // スクリプトは文字に変換されて安全
</script>
HTML

この例では、固定のテンプレートに“動的な部分だけ”をエスケープした文字列で差し込んでいます。それでも、できる限りテンプレートの構造は HTML 側に事前定義し、変動部分は textContent を使うのが基本です。


まとめ

innerHTML は、HTML文字列をその場で展開できる強力なプロパティですが、XSS・イベント消失・性能劣化という落とし穴があります。外部データは常に textContent、構造はテンプレートや既存の DOM を使って組み立て、どうしても innerHTML が必要な場合は厳密に管理された安全なテンプレートに限定する。小さな差し込みでも“イベントと状態が消える”ことを念頭に、可能な限り DOM API で粒度の細かい更新を選ぶ。この方針を守れば、見た目の柔軟性と安全性を両立できます。

タイトルとURLをコピーしました