EagleLand

2016.02.19

iframeの高さを読み込むコンテンツに応じて変える

前提として

である。Twitterがembed機能を提供しているが、そのTwitter側を想定した話だと思ってもらえると良い。

<iframe>でロードされるリソースが<iframe>の高さを決定出来ない

<iframe>の高さは<iframe>を埋め込んでいるHTMLドキュメント側が決定する。HTMLドキュメントの高さはdocument.documentElement.scrollHeightで取得できるが、コンテナ側から<iframe>srcに指定されたHTMLドキュメントを参照することが出来ない。HTMLドキュメントを参照する手段自体は<iframe>contentWindowを通じてiframe.contentWindow.document.documentElementのようにアクセス出来るが、ロードしているリソースがドメインを跨いでいると例外が発生して出来ない。

postMessage()で高さをやり取りする

色々読み漁ったが、今のところwindowオブジェクトのpostMessage()を使うのが正攻法っぽい。以下のように<iframe>でロードしているコンテンツ側からコンテナにメッセージを投げることが出来る。

// iframeでロードされていないケースを考慮し、window.parentの存在をチェックする
if (window.parent) {
  let height = document.documentElement.scrollHeight;
  window.parent.postMessage(height);
}

実際には、複数の<iframe>がロードされている可能性があるので、どの<iframe>とやりとりしているかを管理する必要がある。自分の場合は、まずコンテナ側で<iframe>に割り当てた#id<iframe>でロードしているHTMLドキュメントにpostMessage()で送って、onmessageのハンドラ内でコンテンツの高さを送られた#idとともにコンテナ側にpostMessage()で返答するというやり方をとった。

コンテナ側のJavaScript

window.onmessage = function(e) {
  let json = JSON.parse(e.data);
  document.querySelector(`#${json.id}`).style.height = `${json.height}px`;
};

let iframes = document.querySelectorAll('iframe');
for (let iframe of iframes) {
  iframe.contentWindow.postMessage(iframe.id);
}

<iframe>onloadハンドラでメッセージを送るようにして、srcは後程設定するような実装だと◎。

<iframe>でロードされたHTMLドキュメント側のJavaScript

window.onmessage = function(e) {
  let json = {
    id: e.data,
    height: document.documentElement.scrollHeight
  };
  window.parent.postMessage(JSON.stringify(json));
};

<iframe>でロードするページが非同期描画しているケースは、それを待たないと正確なコンテンツ高を取得出来ない。描画完了をイベントでハンドリングできればいいが、場合によってはタイマーを使うことにもなりそう…。この辺を上手くやっているライブラリとしてdavidjbradshaw/iframe-resizerというものがあるそう(自分は使ってない)。

スペックないのか

iframe要素にはseamlessという属性が定義されているが、ブラウザのサポートが進んでいなかったこともあり、WHATWG側からは先日削除されている

しかし、この流れを受けてか新たな提案もなされている。

iFrame Heightは同等のことをCSSで実現しようというものだ。以下のように<iframe>heightプロパティにmax-contentという値を指定しようと言うもの。

<iframe src="https://1000ch.net"></iframe>
<style>
  iframe {
    height: max-content;
  }
</style>

また、<iframe>でロードされるコンテンツにはレスポンスヘッダにExpose-Height-Cross-Origin: 1;を含んでいる必要がある。潜在的な問題を抱えているものの、ウィンドウ間でpostMessage()をしないとダメというのもあまりにナンセンスだと思うので、是非実現されて欲しい。

タイトルと URL をコピーしました