How to make an iframe responsive without aspect ratio assumption?

流过昼夜 提交于 2021-02-18 08:59:13

问题


How to make an iframe responsive, without assuming an aspect ratio? For example, the content may have any width or height, which is unknown before rendering.

Note, you can use Javascript.

Example:

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

Size this iframe-container so that contents of it barely fit inside without extra space, in other words, there is enough space for content so it can be shown without scrolling, but no excess space. Container wraps the iframe perfectly.

This shows how to make an iframe responsive, assuming the aspect ratio of the content is 16:9. But in this question, the aspect ratio is variable.


回答1:


It is not possible to interact with a different origin iFrame using Javascript so to get the size of it; the only way to do it is by using window.postMessage with the targetOrigin set to your domain or the wildchar * from iFrame source. You can proxy the contents of the different origin sites and use srcdoc, but that is considered a hack and it won't work with SPAs and many other more dynamic pages.

Same origin iFrame size

Suppose we have two same origin iFrames, one of short height and fixed width:

<!-- 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>

and a long height 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>

We can get iFrame size on load event using iframe.contentWindow.document that we'll send to the parent window using 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>

We'll get proper width and height for both iFrames; you can check it online here and see the screenshot here.

Different origin iFrame size hack (not recomended)

The method described here is a hack and it should be used if it's absolutely necessary and there is no other way around; it won't work for most dynamic generated pages and SPAs. The method fetches the page HTML source code using a proxy to bypass CORS policy (cors-anywhere is an easy way to create a simple CORS proxy server and it has an online demo https://cors-anywhere.herokuapp.com) it then injects JS code to that HTML to use postMessage and send the size of the iFrame to the parent document. It even handles iFrame resize (combined with iFrame width: 100%) event and posts the iFrame size back to the parent.

patchIframeHtml:

A function to patch the iFrame HTML code and inject custom Javascript that will use postMessage to send the iFrame size to the parent on load and on resize. If there is a value for the origin parameter, then an HTML <base/> element will be prepended to the head using that origin URL, thus, HTML URIs like /some/resource/file.ext will get fetched properly by the origin URL inside the iFrame.

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:

A function to get a page HTML bypassing the CORS using a proxy if useProxy param is set. There can be additional parameters that will be passed to the postMessage when sending size data.

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();
  });
}

The message event handler function is exactly the same as in "Same origin iFrame size".

We can now load a cross origin domain inside an iFrame with our custom JS code injected:

<!-- 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>

And we'll get the iFrame to size to it's contents full height without any vertical scrolling even using overflow-y: auto for the iFrame body (it should be overflow-y: hidden so we don't get scrollbar flickering on resize).

You can check it online here.

Again to notice that this is a hack and it should be avoided; we cannot access Cross-Origin iFrame document nor inject any kind of things.




回答2:


Their are lots of complications with working out the size of the content in an iframe, mainly due to CSS enabling you to do things that can break how you measure the content size.

I wrote a library that takes care of all these things and also works cross domain, you might find it helpful.

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




回答3:


My solution is on GitHub and JSFiddle.

Presenting a solution for responsive iframe without aspect ratio assumption.

The goal is to resize the iframe when necessary, in other words when the window is resized. This is performed with JavaScript to get the new window size and resize the iframe in consequence.

NB: Don't forget to call the resize function after the page is loaded since the window is not resized by itself after page load.

The code

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;

I spent some time on this. I hope you will enjoy it =)

Edit This is probably the best generic solution. However, when made very small, the iframe is not given the proper size. This is specific to the iframe you are using. It is not possible to code an proper answer for this phenomenon unless you have the code of the iframe, since your code has no way to know which size is prefered by the iframe to be properly displayed.

Only some hacks like the one presented by @Christos Lytras can make the trick but it's never going to be working for every iframe. Just in a particular situation.




回答4:


Do one thing set Iframe container some width and height set CSS property

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

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


来源:https://stackoverflow.com/questions/61407418/how-to-make-an-iframe-responsive-without-aspect-ratio-assumption

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!