レスポンシブなコンテンツをiframeでページに埋め込んでスクロールさせないように表示するとウィンドウ幅によってコンテンツの高さが変わるので、親フレームで高さを調整しないとコンテンツが隠れてしまう場合があります。iframeを埋め込んでいる親フレームにメディアクエリを書けば調整できますが、かなり非効率的な作業です。
先日、JavaScriptを使った方法を見つけたのでこのブログでもシェアします。ちなみに、以下の条件に当てはまる場合に有効です。
- 違うドメインからiframeのコンテンツを読み込む
- iframe内のコンテンツをスクロールさせたくない
- レスポンシブでコンテンツの高さが変わる
同じドメイン内のページを読み込む場合やiframe内でスクロールさせる場合は当てはまらないのでご注意ください。
実装方法の説明
ここではiframeを読み込むページを親フレーム、読み込まれるほうを子フレームと呼びみます。下図のようにJavaScriptのwindow.postMessage()
メソッドを使って子フレームから親フレームにコンテンツの高さを送信します。
親フレームで高さを受け取ってJavaScriptでiframeの高さを調整します。
シンプルですね。
実は、長年この解決策が見つけられずに困っていたんですが、意外にもシンプルに解決できて驚きました。しかし、こういう解決策ってふとしたときに見つかるもんですね。
ソースコードの説明とデモ
まずは簡単なデモをご覧ください。
デモでは以下を実装しています。
- 子フレームでコンテンツの高さを取得
- 子フレームから
postMessage()
でコンテンツの高さを親フレームに送信 - 親フレームで
addEventListner()
を使ってメッセージを受け取る - iframeの高さを調整
子フレームのソースコード
CSS
body {
overflow: hidden;
}
- iframe内でコンテンツがスクロールしないように
body
にoverflow: hidden;
を指定
JavaScript
sendHeight();
function sendHeight(){
var h = document.documentElement.scrollHeight;
parent.postMessage(h, "*");
}
- ページの高さを取得(コンテンツの高さについてのメモ)
parent.postMessage()
メソッドを使って高さを親フレームに送信- このスクリプトをコンテンツが全て読み込まれた後の
</body>
タグの直前に設置しています parent.postMessage(h, "*")
の「*」の所に親フレームのURI(例: https://parashuto.com)を指定するとなお安心
親フレームのソースコード
HTML
<iframe src="http://rwd-book.info/playground/iframe-height/iframe-content.html" height="1000" id="content-frame" frameborder="0"></iframe>
- iframeでコンテンツを読み込みます
postMessage()
をサポートしないブラウザ用にコンテンツの高さの最高値をheight
属性に入れておきます
JavaScript
window.addEventListener('message', function(e) {
if(e.origin=="http://rwd-book.info"){
document.getElementById('content-frame').height = e.data;
}
}, false);
addEventListener()
で子フレームからのメッセージを受け取りますif(e.origin=="http://rwd-book.info")
でメッセージの送信元を確認- デモでは拙著サイトのhttp://rwd-book.info にファイルを設置したので、メッセージがこのドメインから来ていることをチェックしています
document.getElementById('content-frame').height
で、メッセージで送られてきた高さ(e.data
)を設定
以上です!
コンテンツの高さの取得についてのメモ
JavaScriptには高さ関連のプロパティがいくつかあってわかりにくいのですが、今回は<html>
要素のscrollHeight
を使いました。
scrollHeight | 隠れているコンテンツを含む全てのコンテンツで、paddingを含む高さ。 |
---|---|
offsetHeight | 表示されているコンテンツの高さで、padding、border、scrollbarを含む高さ。 |
clientHeight | 表示されているコンテンツの高さで、paddingを含む高さ。 |
参考: stack overflowにあった図 がわかりやすかったです。
ブラウザサポート
この方法はpostMessage()
メソッドに依存しているので、このメソッドをサポートする以下のブラウザで動きます。意外にもサポートが充実していて、インテグレーション系のプロジェクトではかなり使えそうです。
- Chrome 4以降
- Firefox 3以降
- Safari 10.1以降
- iOS Safari 3.2以降
- IE8以降(部分的サポート)
IE8、9ではframse/iframesのみサポート。IE10、11ではある条件下 でサポートされないとのこと。
IE7以下の対応について
IE7以下でも最低限の対応くらいはしておきたいです。
一番簡単な方法は、デフォルトで設定する高さを最大値にしてしまうことです。けっしてエレガントではないですがコンテンツにアクセス出来ないよりは全然ましな対応だと思います。IEのConditional Comments を使ってIE7以下のみにCSSで高さを指定するというのもありですね。
ポリフィルもある ようなので、それらを活用するのもありですね。すみません、ここは検証してません…。
セキュリティについての注意点
MDN web docs に書いてあるように、postMessage()
メソッドは正しく使えばセキュアに違うドメインに設置された複数ページ間でのデータのやり取りができるんですね。
window.postMessage は、安全にクロスドメイン通信を可能にするためのメソッドです。通常、異なった複数のページでのスクリプトはそれらが実行されたページが同じプロトコル(たいてい http)、ポート番号(http のデフォルトは 80)、ホスト(両方のページによって同じ値に設定される document.domain を基準とする)である場合に限りお互いにアクセスすることだけが可能です。window.postMessage は正しく使われたときに安全な方法でこの制限を回避するための制御された仕組みを提供します。
– MDN web docs からの引用
ただ、「正しく使われたときに」というのがキモで、上述したページの「セキュリティに関すること」に書かれているように間違った使い方をするとクロスサイトスクリプティング攻撃を可能にしてしまうので注意が必要です。
任意のウィンドウが、いつでも、ウィンドウの文書の場所にかかわらず、メッセージを送るために、任意の他のウィンドウ上でこのメソッドにアクセスするかもしれません。従って、任意のイベントリスナーはメッセージを受け取る際に、origin あるいは source プロパティを用いて、まず最初にメッセージの送信者の識別情報をチェックしなければなりません。これを軽視することはできません。なぜなら、origin あるいは source プロパティのチェックの失敗はクロスサイトスクリプティング攻撃を可能にするからです。
– MDN web docs からの引用
まとめ
今回紹介したような条件に当てはまる状況って、よくよく考えると結構レアな感じかもしれませんね。でも、似たようなことで困っている方のお役に立てれば幸いです。
では、Happy developing!
コメントを残す