かなり限られたユースケース(後述)でのみ、iframeを便利に使う方法です
何がしたいのか
iframe要素を使うと、現在のHTML文章の中に、別のHTMLファイルを表示することができますよね。参照先のHTMLファイルはURLで指定するため、インターネット上のファイルでもPC上のファイルでも同じように表示できる楽しい要素です
ところが先日、 制約上iframeを使わなければならないが、できれば別のHTMLを参照したくない という状況に遭遇しました。お前は何を言ってるんだ?と言われるかも知れませんが、つまるところこの記事はフロントですべて完結するiframeの使い方を紹介するものです
結論から言うと、HTMLを単なる文字として用意することさえできれば、URLでiframeに渡すことができます。それだけのことなので、コードは非常にシンプルになります
以後、簡略化のためブラウザやエディタの文字コードはUTF-8がデフォルトになっているものとします
試してみる
Blob URL Schemeを使う方法
Blob URL が使える場合はこちらを使います
と念のため言いましたが、ご覧の通りほぼ全てです http://caniuse.com/#feat=blobURIs
// var iframe = document.getElementById('target');
var html = '<b>ただの文字</b>';
var blob = new Blob([html], { type: 'text/html' });
iframe.src = URL.createObjectURL(blob);
これだけでも動作が確認できました
HTMLが<b>
しか書かれていませんが、その辺は通常のHTML表示時と同じく<html><head></head><body></body></html>
をブラウザが補完してくれます(もちろんから書いても動きます)
{ type: 'text/html' }
を付けないとロードに失敗するので注意してください
エスケープしなくても良いというのがポイントでしょうか。さすがBlobくん、優秀です
さて、気になるのはここでjavascriptが動くのかどうかですが…
// var iframe = document.getElementById('target');
var html = `
<b>ただの文字</b>
<script type="text/javascript">
document.body.textContent = 'インジェクションできそう';
</script>
`;
var blob = new Blob([html], { type: 'text/html' });
iframe.src = URL.createObjectURL(blob);
かなり簡略化したコードですが、動作は確認できますね!
「ただの文字」は、javascript実行時に上書きされました
ちなみに、下記のコードは 動きません
// var iframe = document.getElementById('target');
var html = `
<script type="text/javascript">
// Won't work, because in <head>
document.body.textContent = 'インジェクションできない…';
</script>
`;
var blob = new Blob([html], { type: 'text/html' });
iframe.src = URL.createObjectURL(blob);
<script>
から始まるHTMLが与えられると、<head>
の中に入れてしまうためです(標準の仕様なのだろうか)
document.body
が初期化されていないので、参照エラーになります
それにしても、HTMLの内容を動的に決められて、かつjavascriptも実行可能ということは、つまり…
<iframe id="target"></iframe>
<script type="text/javascript">
var iframe = document.getElementById('target');
var html = document.documentElement.innerHTML;
var blob = new Blob([html], { type: 'text/html' });
iframe.src = URL.createObjectURL(blob);
</script>
こっ、これは……!!
とっても……ブラクラですね……
ビデオカメラの映像をテレビに映しながら、そのテレビを撮影する、という遊びを思い出しました
Data URI Schemeを使う方法
Blob URL でできるならということで、Data URI でも試してみます
HTML文字列をData URI に変換するにあたって、こちらの記事を参考にさせていただきました
解説も載っているので、ぜひ参考にしてください。
(コメントでも議論がなされていますが、javascriptの文字列-バイナリ操作は「標準の関数をいかに上手く使うか」「ブラウザ間の違いをいかに吸収するか」が論点となるのが興味深いです)
// var iframe = document.getElementById('target');
var html = '<b>hello こんにちは</b>'
var base64 = btoa(unescape(encodeURIComponent(html)));
iframe.src = 'data:text/html;base64,' + base64;
問題ないですね
ちなみにvar base64
にはどんな文字列が入っているかというと、
PGI+aGVsbG8g44GT44KT44Gr44Gh44GvPC9iPg==
こんな風になっています
なので、上記のコードは、var html
が静的なデータであれば
var base64 = "PGI+aGVsbG8g44GT44KT44Gr44Gh44GvPC9iPg==";
と書くのとと等価です
また、encodeURIComponent
を使っているので、愚かな私などは「ここでURLにしているのかな?」と勘違いしてしまっていたのですが、そうではなく unescape(encodeURIComponent(html))
がセットになっていて、日本語などを含むUTF-8文字列を、それと同等の文字列に変換しているのです
日本語などが含まれていなければ、下記のコードでも同じことが実現できます
// var iframe = document.getElementById('target');
var html = '<b>hello</b>'
var base64 = btoa(html);
iframe.src = 'data:text/html;base64,' + base64;
以上、2つの方法でiframeを使ってみました
使いどころ
iframeを使いたいが、下記のうちいずれかが障害になっており、かつフロントエンドで完結させたいという場合、この方法が役立ちます
- 新しくHTMLファイルを置けない(参照できない)状況
- スタンドアロン、かつ、ローカルホスト環境も用意できない状況
- ロードする前にHTMLの内容を動的に決めたい状況
ねえよ! と盛大にツッコミをいれられるかも知れませんが…(確かにこのご時世ではiframeを使うことすら…)
でも、Webアプリを作るときではなく、既存のページになにか埋め込みたいときと考えれば、ユースケースはそこそこありそうな気がしませんか?