JavaScript
postMessage()を使ってインラインフレーム <iframe> の高さをコンテンツ内容に合わせる

投稿 | | カテゴリー JavaScript
postMessage()を使ってインラインフレーム <iframe> の高さをコンテンツ内容に合わせる

いろいろ制約があってインラインフレームしか使えないけどスクロールは出したくない、とか<iframe>の高さをコンテンツに合わせたい時に、親ウィンドウ側とフレーム側の異なるドメイン間でpostMessage()でクロスドメイン通信を行ってフレーム側の高さを取得するメモです。


ちなみに、親ウィンドウとフレーム側が同じドメインならjQueryでこんな感じです。

$('iframe').height( $('iframe').contents().height() );

別ドメインのページをフレーム内に表示した場合、JavaScriptはセキュリティのためにフレーム内のコンテンツにアクセスできません。自分のページが誰かのサイトのフレーム内で姿を変えて表示されてたらイヤですもんね。

postMessage()は、これら異なるドメイン間(クロスドメイン)で安全にデータのやりとりをするためのメソッドです。間違っていたらいけないので詳しい解説はできませんが、以下の記事とかを参考にしていただくとよいかと思います。

クロスドメインのポリシーファイル

postMessage()を使うには、アクセスする必要のあるドメインで通信が許可されている必要があります。今回は親ウィンドウ側からフレーム側コンテンツへリクエストを行う(ドキュメントの高さを取得する)ので、フレーム側のサーバのルートにクロスドメインのポリシーファイル「crossdomain.xml」を置いて、親ウィンドウに対して通信の許可をしてあげます。

<?xml version="1.0"?>
<cross-domain-policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFile.xsd">
<allow-access-from domain="emwai.jp" />
<allow-access-from domain="*.emwai.jp" />
</cross-domain-policy>

Flashで異なるドメインのリソースにアクセスする際に設置するアレです。<allow-access-from/>タグのdomain属性に許可するドメイン(親ウィンドウ側のドメイン)を指定します。ワイルドカード(どのドメインに対しても許可)も使えますがセキュリティ上、あまりよくありません。複数のドメインを指定できます。サンプルの2つ目はサブドメインをワイルドカードにしています。

ドキュメントの高さをメッセージで受け取る

JavaScriptでは、フレーム内のページでは自身のドキュメントの高さをメッセージとしてキューして、親ウィンドウ側ではそのメッセージを受け取ってインラインフレームの高さを設定するようにします。

フレーム側
function _post_message() {
  // 親ウィンドウ
  var target = parent.postMessage ? parent: (parent.document.postMessage ? parent.document : undefined);
  // 親ウィンドウがあればメッセージ(ドキュメントの高さ)を送る
  if (target) parent.postMessage(document.getElementsByTagName('body')[0].scrollHeight, 'https://blog.emwai.jp');
}

// イベントリスナに登録
if (window.addEventListener) window.addEventListener('load', _post_message, false);
else if (window.attachEvent) window.attachEvent('onload', _post_message, false);

条件分岐しつつloadイベントのリスナに関数を登録してますが、高さの取得だけなのでjQueryならready()でOKだと思います。postMessage()の第一引数にはメッセージの内容、第二引数には親ウィンドウ側のドメインを指定します。
window.postMessage › 構文 | MDN

親ウィンドウ側
(function( iframe ) {
  if ( !iframe ) return;

  function _receive_message(e) {
    // メッセージの送信者が信頼できるものかどうか(該当しない場合は何もしない)
    if ( e.origin !== 'https://emwai.jp' ) return;
    // インラインフレームの高さを受け取った内容(フレーム側ドキュメントの高さ)に変更する
    iframe.style.height = (e.data + 40) + 'px';
  }

  // イベントリスナに登録
  if (window.addEventListener) window.addEventListener('message', _receive_message, false);
  else if (window.attachEvent) window.attachEvent('onmessage', _receive_message, false);
})( document.getElementById('iframe') );

親ウィンドウ側でも送信側の識別情報(originあるいはsource)を確かめて、信頼できるメッセージのみ処理することが望ましいです。高さの設定はもらった値にピッタリだとスクロールが出ちゃうことがあるので少しだけ高さ(40px)を足しています(例では端折ってますが、当然ながら要素のプロパティへアクセスできるタイミングで実行します)。

resizeイベントが発生したら高さを再設定」みたいなレスポンシブな対応もいいかもしれません。

実行結果