最終更新日:190910 原本2017-12-20

別のHTMLを用意せずにiframeを表示する

かなり限られたユースケース(後述)でのみ、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);

スクリーンショット 2016-10-10 12.28.50.png

これだけでも動作が確認できました
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);

スクリーンショット 2016-10-10 12.41.52.png

かなり簡略化したコードですが、動作は確認できますね!
「ただの文字」は、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>

こっ、これは……!!

スクリーンショット 2016-10-10 12.58.22.png

とっても……ブラクラですね……

ビデオカメラの映像をテレビに映しながら、そのテレビを撮影する、という遊びを思い出しました

Data URI Schemeを使う方法

Blob URL でできるならということで、Data URI でも試してみます

HTML文字列をData URI に変換するにあたって、こちらの記事を参考にさせていただきました

javascriptでBase64

解説も載っているので、ぜひ参考にしてください。
(コメントでも議論がなされていますが、javascriptの文字列-バイナリ操作は「標準の関数をいかに上手く使うか」「ブラウザ間の違いをいかに吸収するか」が論点となるのが興味深いです)

// var iframe = document.getElementById('target');

var html = '<b>hello こんにちは</b>'
var base64 = btoa(unescape(encodeURIComponent(html)));
iframe.src = 'data:text/html;base64,' + base64;

スクリーンショット 2016-10-10 13.52.57.png

問題ないですね

ちなみに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を使いたいが、下記のうちいずれかが障害になっており、かつフロントエンドで完結させたいという場合、この方法が役立ちます

  1. 新しくHTMLファイルを置けない(参照できない)状況
  2. スタンドアロン、かつ、ローカルホスト環境も用意できない状況
  3. ロードする前にHTMLの内容を動的に決めたい状況

ねえよ! と盛大にツッコミをいれられるかも知れませんが…(確かにこのご時世ではiframeを使うことすら…)

でも、Webアプリを作るときではなく、既存のページになにか埋め込みたいときと考えれば、ユースケースはそこそこありそうな気がしませんか?

参考