JavaScriptの変数の中身ってどうやってHTMLに渡せばいいんだ?
アラートやコンソールには表示できたけど、JavaScriptの処理結果をHTMLに反映させたい・・・
こんにちは。はじめてJavaScriptに触れてから15年ちょっと。最近の進歩の速さに驚きを隠せないテックライターの平山です。フロントもサーバサイドもJavaScriptで行けるとか、すごい時代になったものですね。
さて、JavaScriptを学習しはじめた頃はアラートやコンソールを使って、プログラムの結果を表示させるのがよくあるパターンです。ですが、学習を進めていく途中や、実際に自分でオリジナルのものを組み立てだしたときに、はたと気づく問題があります。
はて、JavaScriptの結果はどうやってHTMLに渡せばいいんだ?
そう。JavaScriptの学習をゴリゴリ進めていっても、HTMLとの連動方法がわからないと、JavaScriptらしい動きのあるWebページは実現できないんです。笑い話のようですが、意外と盲点なポイントなんですね。
そこでこの記事では、一番の基礎となる、JavaScriptの変数をHTMLにどう反映させていくか?という方法をお伝えします。やり方は複数ありますが、それぞれ一長一短のある方法なので、全部きっちり習得しておきたいですね。
そして、最後にJavaScriptでHTMLを動的に変更する上での、セキュリティー上のリスクと対策を紹介します。外部から入力を受け付けてHTMLを書き換える場合、セキュリティー上の問題は常につきまといます。少々面倒な部分ですが、必要なことですので、ぜひ考え方を理解してください。
それでは早速行ってみましょう!
JavaScriptの変数をHTMLにわたす準備
まずはJavaScriptの変数をHTMLにわたす方法を紹介します。今回は一覧性を重視してHTMLファイルの中にscriptタグでJavaScriptを記述して行きますが、外部ファイルにしても方法は同じですので、上手に対応してください。
今回はサンプルプログラムとして以下のものを書き換えていきます。
<html> <body> <p>ボタンをクリック!</p> <div id="edit_area"></div> <input type="button" value="Check" onclick="myfunc()"> <script> var myfunc = function () { <!-- ここの中身を書き換えていきます --> } </script> </body> </html>
ボタンをクリックすると、myfuncが実行されるシンプルなプログラムです。サンプルの中身がいまいちよくわからない方はこちらの記事を参考にしてみてください。
.innerHTMLや.innerTextを使う
まずは.innerHTMLや.textContentを使う方法を紹介します。これらのプロパティはHTML要素の中身を変更する際に利用します。document.getElementById(“ID名”)と組み合わせて使うことが非常に多いですね。お約束的な書き方なので、ぜひこの機会に覚えてしまいましょう。
getElementById? innerHTML?という方はDOM操作の基本がわかっていない可能性が高いです。そんな方は是非こちらの記事でDOMの基礎を習得してください。
具体的な書き方と実行結果は以下の通りです。
.innerHTML
<script> var myfunc = function () { var str = ".innerHTMLで変数渡し!"; document.getElementById('edit_area').innerHTML = str ; } </script>
実行結果
.textContent
<script> var myfunc = function () { var str = ".textContentで変数渡し!"; document.getElementById('edit_area').textContent = str ; } </script>
実行結果
.innerHTMLと.textContentで同じような結果が出力されましたが、使い分け方法は簡単です。次のサンプルと実行結果を見てみてください。
.innerHTML
<script> var myfunc = function () { var str = ".innerHTML<br>で<br>変数渡し!"; document.getElementById('edit_area').innerHTML = str ; } </script>
実行結果
.textContent
<script> var myfunc = function () { var str = ".textContent<br>で<br>変数渡し!"; document.getElementById('edit_area').textContent = str ; } </script>
実行結果
.innerHTMLでは<br>タグが解釈されて文章が改行されていますが、.textContentでは<br>タグが解釈されずにそのまま文字列として出力されているのがわかりますね。
つまり、JavaScriptの出力がHTMLタグを含んでおり、それを反映させたい場合は.innerHTML。逆に中身のテキストだけを書き換えたい場合は.textContentという使い分けができます。
なお、これらに加えて.innetTextというプロパティもあります。できることはtextContentとほぼほぼおなじという代物です。似たような機能が別名で存在するのはブラウザ覇権争いの名残みたいなものなので、そういうものだと思って諦めてください。
ブラウザ覇権争いに絡んで、昔はIEだと.textContentが使えないとか、Firefoxだと.innerTextが使えないとかありました。ですが、現在はモダンなブラウザであれば今回紹介した3種のプロパティはどれでも使えるので、違いにそこまで拘る必要はないでしょう。
タグを解釈してくれる.innerHTMLは便利ですが、外部からの入力を受け付けて、それをHTMLに反映させる場合、セキュリティ上の問題が発生することがあります。詳しい話は3章のセキュリティ上の注意点でみていきましょう。
document.write()を使う
続いて紹介するのがdocument.write()を使った方法です。最近ではあまり使われない方法なのですが、挙動がわかりやすいために、学習段階では見かける機会も時々ありますね。
書式
document.open(); document.write("書き込む内容"); document.close();
最近のブラウザはdocument.open();、document.close();を省略してもよしなに計らってくれますが、本来はopen,closeをセットで書くのが決まりです。
具体例をみてみましょう。
<script> var myfunc = function () { var str = "document.write()で変数渡し!"; document.open(); document.write(str); document.close(); } </script>
実行結果
実際に実行して構造を見ていだだくとわかりますが、bodyタグの中身がまるまるdocument.write()の変数に置き換わっています。その結果、ボタンが上書きされてなくなってしまいました。
このように全面的な書き換えを行う場合、document.write()は出番があります。裏を返すと、ページの一部だけを書き換えるようなやり方にはあまり向いていません。
こちらも.innerHTMLと同様にタグを含む文字列を渡すと、HTMLにタグとして解釈されて反映されます。そのため、外部からの入力を受け付ける場合はセキュリティ上のリスクを抱えていることも覚えておいてください。
.insertAdjacentHTML()を使う
最後に紹介するのは、insertAdjacent系のメソッドです。
今まで紹介してきたinnerHTMLやdocument.write()は内部の構造を上書きする変更でした。これを破壊的な変更といいます。
ですが、場合によっては上書きよりも、既存の構造に追加したい、という状況もあることでしょう。このとき役に立つのがこれから紹介する.insertAdjacentHTML()などのメソッドです。
.insertAdjacentHTML()は特定のポジションにテキストを挿入するメソッドです。
書式は以下の通りです。
.insertAdjacentHTML(ポジション , テキスト);
テキストには文字列が入り、ポジションには次の4つの文字列のいずれかを記入します。
- “beforebegin”:要素の直前に挿入
- “afterbegin”:要素内部の、最初の子要素の前に挿入
- “beforeend”:要素内部の、最後の子要素の後に挿入
- “afterend”:要素の直後に挿入
言葉だけで見ると少々わかりにくいので、実際にサンプルを動かて動作をみてみましょう。
<script> var myfunc = function () { var id = document.getElementById("edit_area"); id.insertAdjacentHTML("afterbegin","<p>afterbeginの位置!</p>"); id.insertAdjacentHTML("beforebegin","<p>beforebeginの位置!</p>"); id.insertAdjacentHTML("afterend","<p>afterendの位置!</p>"); id.insertAdjacentHTML("beforeend","<p>beforeendの位置!</p>"); } </script>
実行結果
bodyタグの内部構造
<p>ボタンをクリック!</p> <p>afterbeginの位置!</p> <div id="edit_area"><p>beforebeginの位置!</p><p>afterendの位置!</p></div> <p>beforeendの位置!</p> <input type="button" value="Check" onclick="myfunc()">
このサンプルでは、<div id="edit_area">を起点に.insertAdjacentHTML()の配置が行われています。afterbegin,beforebeginは<div id="edit_area">の前後。afterend,beforeendは</div>の前後にテキストが挿入しているのが確認できましたね。
配置は文章を読んだだけでは頭に入ってきにくいので、実際にプログラムを動かして体験して体に叩き込みましょう。
あと、今回は説明の都合上変数を使っていませんが、テキスト部分を変数に置き換えることはもちろん可能です。
節のはじめにinsertAdjacent系といいましたが、このメソッドもinnerHTMLのように使い分けができるメソッドがいくつかあります。
まずは今回紹介したinsertAdjacentHTML。これはテキスト部分のタグを解釈してHTMLに反映できるメソッドです。そのため、便利ですがセキュリティリスクがあることも理解しておく必要があります。
次にinsertAdjacentText()。これは.textContentのようにテキストのタグを解釈せず、そのままHTMLに表示するメソッドです。用途が限定されている分、セキュリティ的にはリスクの少ないメソッドになりますね。
あと、似たような名前で.insertAdjacentElement()というメソッドも存在します。これはdivタグといったHTML要素を挿入するために使うメソッドです。今回はあまり関係ないですね。
セキュリティ上の注意点
さて、3パターンほどJavaScriptの変数をHTMLに渡して反映させる方法を紹介してきましたが、ところどころでセキュリティの話が出てきました。セキュリティというと難しそうに感じる方もいるかも知れませんが、この事例はそこまで難しい内容ではないので、きっちり習得してセキュリティの問題がないコードを書けるようになりましょう。
外部からの入力を受け付ける場合の注意
今回はJavaScriptの変数をHTMLにわたす、というところで解説を進めてきましたが、セキュリティ上のリスクはどこに潜んでいると思いますか?答えは、外部からの入力を受け付ける、という部分です。
今回紹介した仕組みを使えば、簡単に外部からのテキストコメントを受け付ける、いわゆるコメント機能が実装できます。ですが、その際に次のようなコメントが投稿されたらどうなるでしょうか?
<a href="https://悪意あるリンク.com">適当なコメント</a>
このようなコメントを.innerHTMLなどでHTMLに渡してしまうと、悪意あるリンクの付いたコメントがそのままHTMLに反映されてしまいます。
今回はごく簡単な例で確認しましたが、たとえばリンクではなくて、スクリプトが投稿された場合、悪意あるスクリプトがそのままHTMLに反映されてしまうわけです。これをDOM Based XSSと呼びます。
DOM Based XSSについてはIPAのこちらのページで詳しく紹介されています。
参照: IPAテクニカルウォッチ 『DOM Based XSS』に関するレポート
それでは具体的な対処方法をみていきましょう
.innerHTML,document.write(),insertAdjacentHTML()を使わない
まず手っ取り早いのが、必要以外の場所で上記のメソッド、プロパティを使わない方法です。外部から入力可能な部分でテキストがHTMLに解釈されてしまうのが問題なので、テキストはテキストとして出力するものを使ったほうが安全です。
具体的には
- .innerHTMLの代わりに.textContent
- .insertAdjacentHTML()の代わりに.insertAdjacentText()
これらを使うことでテキストがHTMLに解釈される問題を回避できます。
エスケープ処理を実装する方法
ただ、どうしても外部からの入力に対してHTML解釈が必要な場面もあるかもしれません。そういった場合の対策はエスケープ処理です。
エスケープ処理とは、特定の文字を別の文字に置き換える処理のこと。今回で言えばタグを構成する<,>,",’,&の要素を置き換えてしまえば、攻撃者はHTMLに問題のあるリンクやスクリプトを仕込めなくなります。
具体的には入力された文字列に対して、エスケープ処理を最初にかけて、それ以降の処理を行う、という流れです。
サンプルは次のようになります。
<html> <body> <p>ボタンをクリック!</p> <input type="text" id="text_box"> <div id="edit_area"></div> <input type="button" value="Check" onclick="myfunc('text_box')"> <script> var myfunc = function (id) { var str = document.getElementById(id).value; var edit_area = document.getElementById("edit_area"); str = str.replace(/</g, "<"); str = str.replace(/>/g, ">"); str = str.replace(/"/g, """); str = str.replace(/'/g, "'"); str = str.replace(/&/g, "&"