アスペクト比を想定せずにiframeを応答させる方法は?


14

アスペクト比を想定せずにiframeをレスポンシブにする方法は?たとえば、コンテンツには幅や高さがあり、レンダリング前は不明です。

JavaScriptを使用できることに注意してください。

例:

<div id="iframe-container">
    <iframe/>
</div>

iframe-container余計なスペースがなくてもコンテンツがかろうじて収まるようにサイズを調整します。つまり、コンテンツを表示するのに十分なスペースがあり、スクロールせずに表示できますが、余分なスペースはありません。コンテナはiframeを完全にラップします。

これは、コンテンツのアスペクト比が16:9であると想定して、iframeをレスポンシブにする方法を示しています。しかし、この質問では、アスペクト比は可変です。


1
編集@TravisJで明確にしましょう
フェリット

2
iframe内のコンテンツのサイズを計算することは可能ですが、正確にどのように実行する必要があるかは、コンテンツ自体と使用されるCSSの量に依存します(絶対に配置されたコンテンツは測定が非常に困難です)。cssリセットファイルを使用している場合、結果はリセットファイルが使用されていないページとは異なり、ブラウザによって異なります。iframeとその親のサイズの設定は簡単です。そのため、ページの構造、特に使用されているCSSリセットファイルの名前(または、一般的なファイルを使用していない場合は実際のCSS)の詳細情報が必要になります。
Teemu

2
iframeに読み込まれたドキュメントを制御できますか?つまり、そうしないと、あなたにできることは何もありません。すべてをiframeドキュメント内で(またはそのドキュメントにアクセスして)行う必要があります。それ以外の場合、これはまったく不可能です。
Teemu

1
いいえ、それは不可能です。developer.mozilla.org/en- US/ docs/Web/ Security/ …を参照してください。メインページは、セキュリティ上の理由からiframe内のクロスドメインドキュメントにアクセスできません(逆も同様です)。これは、iframeに表示されているドキュメントを制御できる場合にのみ回避できます。
Teemu

1
... 15年のインターネットは不可能だと言っているだけでは不十分ですか?これについて新しい質問が本当に必要ですか?
カイドウ

回答:


8

JavaScriptを使用して別のオリジンiFrameとやり取りしてサイズを取得することはできません。これを行う唯一の方法は、ドメインに設定するか、iFrameソースのwildchar を使用window.postMessageすることです。さまざまな配信元サイトのコンテンツをプロキシしてを使用できますが、これはハックと見なされ、SPAやその他の多くの動的ページでは機能しません。targetOrigin*srcdoc

同じ原点のiFrameサイズ

2つの同じ原点iFrameがあり、1つは高さが低く、幅は固定されているとします。

<!-- iframe-short.html -->
<head>
  <style type="text/css">
    html, body { margin: 0 }
    body {
      width: 300px;
    }
  </style>
</head>
<body>
  <div>This is an iFrame</div>
  <span id="val">(val)</span>
</body>

長い高さのiFrame:

<!-- iframe-long.html -->
<head>
  <style type="text/css">
    html, body { margin: 0 }
    #expander {
      height: 1200px; 
    }
  </style>
</head>
<body>
  <div>This is a long height iFrame Start</div>
  <span id="val">(val)</span>
  <div id="expander"></div>
  <div>This is a long height iFrame End</div>
  <span id="val">(val)</span>
</body>

load使用iframe.contentWindow.documentして親ウィンドウに送信するイベントを使用して、iFrameサイズを取得できますpostMessage

<div>
  <iframe id="iframe-local" src="iframe-short.html"></iframe>
</div>
<div>
  <iframe id="iframe-long" src="iframe-long.html"></iframe>
</div>

<script>

function iframeLoad() {
  window.top.postMessage({
    iframeWidth: this.contentWindow.document.body.scrollWidth,
    iframeHeight: this.contentWindow.document.body.scrollHeight,
    params: {
      id: this.getAttribute('id')
    }
  });
}

window.addEventListener('message', ({
  data: {
    iframeWidth,
    iframeHeight,
    params: {
      id
    } = {}
  }
}) => {
  // We add 6 pixels because we have "border-width: 3px" for all the iframes

  if (iframeWidth) {
    document.getElementById(id).style.width = `${iframeWidth + 6}px`;
  }

  if (iframeHeight) {
    document.getElementById(id).style.height = `${iframeHeight + 6}px`;
  }

}, false);

document.getElementById('iframe-local').addEventListener('load', iframeLoad);
document.getElementById('iframe-long').addEventListener('load', iframeLoad);

</script>

両方のiFrameに適切な幅と高さを取得します。ここでオンライン確認し、スクリーンショットをここで確認できます

異なる発信元のiFrameサイズのハック非推奨

ここで説明する方法はハックであり、絶対に必要で他に方法がない場合に使用する必要があります。ほとんどの動的に生成されたページとSPA では機能ません。このメソッドは、プロキシを使用してページのHTMLソースコードをフェッチし、CORSポリシーをバイパスcors-anywhereします(シンプルなCORSプロキシサーバーを作成する簡単な方法であり、オンラインデモがありますhttps://cors-anywhere.herokuapp.com)。次に、そのHTMLにJSコードを挿入して、使用postMessageするサイズを送信します。親ドキュメントへのiFrame。さらに、iFrame resize(iFrame と組み合わせたwidth: 100%)イベントを処理し、iFrameのサイズを親に返します。

patchIframeHtml

iFrameのHTMLコードと使用する注入カスタムJavascriptのパッチを適用するための機能postMessage上の親にiFrameのサイズを送信するためにload、オンをresizeoriginパラメータの値がある場合、HTML <base/>要素はその元のURLを使用して先頭に追加されます。したがって、HTMLのURIなど/some/resource/file.extはiFrame内の元のURLによって適切にフェッチされます。

function patchIframeHtml(html, origin, params = {}) {
  // Create a DOM parser
  const parser = new DOMParser();

  // Create a document parsing the HTML as "text/html"
  const doc = parser.parseFromString(html, 'text/html');

  // Create the script element that will be injected to the iFrame
  const script = doc.createElement('script');

  // Set the script code
  script.textContent = `
    window.addEventListener('load', () => {
      // Set iFrame document "height: auto" and "overlow-y: auto",
      // so to get auto height. We set "overlow-y: auto" for demontration
      // and in usage it should be "overlow-y: hidden"
      document.body.style.height = 'auto';
      document.body.style.overflowY = 'auto';

      poseResizeMessage();
    });

    window.addEventListener('resize', poseResizeMessage);

    function poseResizeMessage() {
      window.top.postMessage({
        // iframeWidth: document.body.scrollWidth,
        iframeHeight: document.body.scrollHeight,
        // pass the params as encoded URI JSON string
        // and decode them back inside iFrame
        params: JSON.parse(decodeURIComponent('${encodeURIComponent(JSON.stringify(params))}'))
      }, '*');
    }
  `;

  // Append the custom script element to the iFrame body
  doc.body.appendChild(script);

  // If we have an origin URL,
  // create a base tag using that origin
  // and prepend it to the head
  if (origin) {
    const base = doc.createElement('base');
    base.setAttribute('href', origin);

    doc.head.prepend(base);
  }

  // Return the document altered HTML that contains the injected script
  return doc.documentElement.outerHTML;
}

getIframeHtml

useProxyparamが設定されている場合、プロキシを使用してCORSをバイパスするページHTMLを取得する関数。postMessageサイズデータを送信するときにに渡される追加のパラメーターがある場合があります。

function getIframeHtml(url, useProxy = false, params = {}) {
  return new Promise(resolve => {
    const xhr = new XMLHttpRequest();

    xhr.onreadystatechange = function() {
      if (xhr.readyState == XMLHttpRequest.DONE) {
        // If we use a proxy,
        // set the origin so it will be placed on a base tag inside iFrame head
        let origin = useProxy && (new URL(url)).origin;

        const patchedHtml = patchIframeHtml(xhr.responseText, origin, params);
        resolve(patchedHtml);
      }
    }

    // Use cors-anywhere proxy if useProxy is set
    xhr.open('GET', useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url, true);
    xhr.send();
  });
}

メッセージイベントハンドラー関数は、「同じ原点のiFrameサイズ」とまったく同じです。

カスタムJSコードが挿入されたiFrame内にクロスオリジンドメインを読み込むことができます。

<!-- It's important that the iFrame must have a 100% width 
     for the resize event to work -->
<iframe id="iframe-cross" style="width: 100%"></iframe>

<script>
window.addEventListener('DOMContentLoaded', async () => {
  const crossDomainHtml = await getIframeHtml(
    'https://en.wikipedia.org/wiki/HTML', true /* useProxy */, { id: 'iframe-cross' }
  );

  // We use srcdoc attribute to set the iFrame HTML instead of a src URL
  document.getElementById('iframe-cross').setAttribute('srcdoc', crossDomainHtml);
});
</script>

そして、我々は、任意の垂直方向のスクロールにも使用しなくても、それの内容に完全な高さをサイズにiFrameを取得しますoverflow-y: auto(iFrameのボディのために、それがあるべきoverflow-y: hidden我々はリサイズのちらつきスクロールバーを取得しないように)。

こちらからオンライン確認できます

ここでもハッキングであり、回避する必要があることに注意してくださいCross-Origin iFrameドキュメントにアクセスしたり、あらゆるものを挿入したりすることできません


1
ありがとう!すばらしい答えです。
ferit

1
ありがとうございました。残念ながらcors-anywhere、現在ダウンしているので、デモページを見ることができません。著作権上の理由から、外部サイトのスクリーンショットを追加したくありません。それが長い間ダウンしている場合は、他のCORSプロキシを追加します。
Christos Lytras

2

それらは主にコンテンツサイズの測定方法を壊す可能性のあることをCSSが可能にするために、iframe内のコンテンツのサイズを計算することで多くの複雑さを伴います。

私はこれらすべてのことを処理し、クロスドメインでも機能するライブラリを書きました。

https://github.com/davidjbradshaw/iframe-resizer


1
涼しい!それをチェックします!
ferit

0

私の解決策はGitHubJSFiddleです。

アスペクト比を想定せずにレスポンシブiframeのソリューションを提示します。

目標は、必要に応じて、つまりウィンドウのサイズが変更されたときに、iframeのサイズを変更することです。これはJavaScriptで実行され、結果として新しいウィンドウサイズを取得し、iframeのサイズを変更します。

注意:ページのロード後にウィンドウ自体のサイズが変更されることはないため、ページのロード後にサイズ変更関数を呼び出すことを忘れないでください。

コード

index.html

<!DOCTYPE html>
<!-- Onyr for StackOverflow -->

<html>

<head> 
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
    <title>Responsive Iframe</title>
    <link rel="stylesheet" type="text/css" href="./style.css">
</head>

<body id="page_body">
    <h1>Responsive iframe</h1>

    <div id="video_wrapper">
        <iframe id="iframe" src="https://fr.wikipedia.org/wiki/Main_Page"></iframe>
    </div>

    <p> 
        Presenting a solution for responsive iframe without aspect
        ratio assumption.<br><br>
    </p>

    <script src="./main.js"></script>
</body>

</html>

style.css

html {
    height: 100%;
    max-height: 1000px;
}

body {
    background-color: #44474a;
    color: white;
    margin: 0;
    padding: 0;
}

#videoWrapper {
    position: relative;
    padding-top: 25px;
    padding-bottom: 100px;
    height: 0;
    margin: 10;
}
#iframe {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

main.js

let videoWrapper = document.getElementById("video_wrapper");

let w;
let h;
let bodyWidth;
let bodyHeight;

// get window size and resize the iframe
function resizeIframeWrapper() {
    w = window.innerWidth;
    h = window.innerHeight;

    videoWrapper.style["width"] = `${w}px`;
    videoWrapper.style["height"] = `${h - 200}px`;
}

// call the resize function when windows is resized and after load
window.onload = resizeIframeWrapper;
window.onresize = resizeIframeWrapper;

私はこれに少し時間を費やしました。楽しんでいただければ幸いです=)

編集 これはおそらく最良の一般的なソリューションです。ただし、非常に小さくすると、iframeに適切なサイズが与えられません。これは、使用しているiframeに固有のものです。iframeのコードがなければ、この現象に適切な答えをコーディングすることはできません。コードには、iframeが適切に表示するために優先するサイズを知る方法がないためです。

@Christos Lytrasによって提示されたような一部のハックのみがトリックを作成できますが、すべてのiframeで機能することは決してありません。特定の状況でのみ。


試していただきありがとうございます!ただし、期待どおりに機能していません。iframeのコンテンツが小さい場合でも、コンテナーにはまだ余分なスペースがあります。これを見てください:jsfiddle.net/6p2suv07/3 iframeのsrcを変更しました。
ferit

「余分なスペース」の問題を理解できません。ウィンドウの最小サイズは幅100ピクセルです。iframeはコンテナーに適合します。iframe内のレイアウトは私の制御下にありません。私はあなたの質問に答えました。さらにヘルプが必要な場合は編集してください
Onyr

写真を入れて説明する
Onyr

iframeは、コンテナーがそれに与えるすべてのスペースを取ります。つまり、iframeコンテンツが小さい場合、それが取ることができるすべてのスペースを必要としません。したがって、コンテナはiframeに十分なスペースを提供する必要があります。
ferit

iframeの高さは、スペースの高さが例のウィンドウによって制限されているためです。あれ?あなたの例はあなたが使うiframeに固有のものです、私は受け入れられるべき一般的な答えを与えました。もちろん、コードを微調整してiframeにうまく合わせることができますが、iframeの背後にあるコードを知る必要があります
Onyr

-1

Iframeコンテナに幅と高さを設定するCSSプロパティを設定する

.iframe-container{
     width:100%;
     min-height:600px;
     max-height:100vh;
}

.iframe-container iframe{
     width:100%;
     height:100%;
     object-fit:contain;
}

コンテンツが600px未満の場合はどうなりますか?
フェリー

高さが600の場合、オブジェクトフィットを含むように設定したのでiframeは影響を受けません
Aslam khan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.