別の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



```javascript
// 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](https://qiita-image-store.s3.amazonaws.com/0/115551/2409ed77-2f5d-cbea-8dcd-018f0a9558fb.png "スクリーンショット 2016-10-10 12.28.50.png")


これだけでも動作が確認できました
HTMLが`<b>`しか書かれていませんが、その辺は通常のHTML表示時と同じく`<html><head></head><body></body></html>`をブラウザが補完してくれます（もちろん<html>から書いても動きます）

`{ type: 'text/html' }`を付けないとロードに失敗するので注意してください

エスケープしなくても良いというのがポイントでしょうか。さすがBlobくん、優秀です

さて、気になるのはここでjavascriptが動くのかどうかですが…

```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](https://qiita-image-store.s3.amazonaws.com/0/115551/6bcbe70c-cf3a-dfca-cf63-81aca8a8bcbb.png "スクリーンショット 2016-10-10 12.41.52.png")

かなり簡略化したコードですが、動作は確認できますね！
「ただの文字」は、javascript実行時に上書きされました

ちなみに、下記のコードは **動きません**

```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も実行可能ということは、つまり…

```html
<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](https://qiita-image-store.s3.amazonaws.com/0/115551/96d4cbb2-955d-560d-70d5-f4863e4aa85d.png "スクリーンショット 2016-10-10 12.58.22.png")


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

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


### Data URI Schemeを使う方法

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

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

[javascriptでBase64](http://qiita.com/weal/items/1a2af81138cd8f49937d)

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

```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](https://qiita-image-store.s3.amazonaws.com/0/115551/05e90937-a34d-b0b5-7952-b6265b125b59.png "スクリーンショット 2016-10-10 13.52.57.png")


問題ないですね

ちなみに`var base64`にはどんな文字列が入っているかというと、

> PGI+aGVsbG8g44GT44KT44Gr44Gh44GvPC9iPg==

こんな風になっています

なので、上記のコードは、`var html`が静的なデータであれば
`var base64 = "PGI+aGVsbG8g44GT44KT44Gr44Gh44GvPC9iPg==";`
と書くのとと等価です

また、`encodeURIComponent`を使っているので、愚かな私などは「ここでURLにしているのかな？」と勘違いしてしまっていたのですが、そうではなく `unescape(encodeURIComponent(html))` がセットになっていて、日本語などを含むUTF-8文字列を、それと同等の文字列に変換しているのです

日本語などが含まれていなければ、下記のコードでも同じことが実現できます

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

var html = '<b>hello</b>'
var base64 = btoa(html);
iframe.src = 'data:text/html;base64,' + base64;
```


以上、２つの方法でiframeを使ってみました


## 使いどころ 
iframeを使いたいが、下記のうちいずれかが障害になっており、かつフロントエンドで完結させたいという場合、この方法が役立ちます

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

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

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

## 参考

- [javascriptでBase64](http://qiita.com/weal/items/1a2af81138cd8f49937d)

- [escape 関数と unescape 関数 - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Predefined_Functions/escape_and_unescape_Functions)