如何在没有纵横比假设的情况下使 iframe 响应?
How to make an iframe responsive without aspect ratio assumption?
如何在不假定宽高比的情况下使 iframe 具有响应性?例如,内容可能有任何宽度或高度,这在呈现之前是未知的。
请注意,您可以使用 Javascript。
示例:
<div id="iframe-container">
<iframe/>
</div>
调整此 iframe-container
的大小,使其内容在没有额外 space 的情况下勉强能容纳在里面,换句话说,有足够的 space 内容,因此无需滚动即可显示,但没有多余的 space。容器完美包裹 iframe。
This 展示了如何使 iframe 响应,假设内容的纵横比为 16:9。但是在这道题中,纵横比是可变的。
提出一个没有宽高比假设的响应式 iframe 的解决方案。
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.
注意:不要忘记在页面加载后调用调整大小函数,因为 window 在页面加载后不会自行调整大小。
代码
index.html
<!DOCTYPE html>
<!-- Onyr for Whosebug -->
<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 提出的那样的一些 hack 可以解决这个问题,但它永远不会适用于每个 iframe。只是在特定情况下。
无法使用 Javascript 与不同来源的 iFrame 交互以获取其大小;唯一的方法是使用 window.postMessage
并将 targetOrigin
设置为您的域或来自 iFrame 源的通配符 *
。您可以代理不同源站点的内容并使用 srcdoc
,但这被认为是 hack,它不适用于 SPA 和许多其他更动态的页面。
同源 iFrame 大小
假设我们有两个同源 iFrame,一个是短高度,一个是固定宽度:
<!-- 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>
我们可以使用 iframe.contentWindow.document
在 load
事件上获取 iFrame 大小,我们将使用 postMessage
:
发送给父 window
<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 获得正确的宽度和高度;你可以上网查here and see the screenshot here.
不同来源的 iFrame 大小 hack(不推荐)
这里描述的方法是一个 hack,如果绝对必要并且没有其他办法,应该使用它; 它对大多数动态生成的页面和 SPA 无效。该方法使用代理获取页面 HTML 源代码以绕过 CORS 策略(cors-anywhere
是创建简单 CORS 代理服务器的简单方法,它有在线演示 https://cors-anywhere.herokuapp.com
) 然后将 JS 代码注入 HTML 以使用 postMessage
并将 iFrame 的大小发送到父文档。它甚至可以处理 iFrame resize
(结合 iFrame width: 100%
)事件并将 iFrame 大小发回父级。
patchIframeHtml
:
修补 iFrame HTML 代码并注入自定义 Javascript 的函数,它将使用 postMessage
将 iFrame 大小发送到 load
和 resize
。如果 origin
参数有一个值,那么 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
:
如果设置了 useProxy
参数,则使用代理获取页面 HTML 绕过 CORS 的函数。发送大小数据时,可以有其他参数将传递给 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();
});
}
消息事件处理函数和"Same origin iFrame size".
完全一样
我们现在可以通过注入自定义 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
所以我们不要让滚动条在调整大小时闪烁 ).
你可以上网查here。
再次注意这是一个hack并且应该避免;我们不能访问跨源 iFrame 文档,也不能注入任何东西。
计算 iframe 中内容的大小有很多复杂性,主要是因为 CSS 使您能够做一些可以破坏您衡量内容大小的事情。
我写了一个库来处理所有这些事情并且还可以跨域工作,你可能会发现它很有帮助。
做一件事设置 Iframe 容器一些宽度和高度设置 CSS 属性
.iframe-container{
width:100%;
min-height:600px;
max-height:100vh;
}
.iframe-container iframe{
width:100%;
height:100%;
object-fit:contain;
}
如何在不假定宽高比的情况下使 iframe 具有响应性?例如,内容可能有任何宽度或高度,这在呈现之前是未知的。
请注意,您可以使用 Javascript。
示例:
<div id="iframe-container">
<iframe/>
</div>
调整此 iframe-container
的大小,使其内容在没有额外 space 的情况下勉强能容纳在里面,换句话说,有足够的 space 内容,因此无需滚动即可显示,但没有多余的 space。容器完美包裹 iframe。
This 展示了如何使 iframe 响应,假设内容的纵横比为 16:9。但是在这道题中,纵横比是可变的。
提出一个没有宽高比假设的响应式 iframe 的解决方案。
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.
注意:不要忘记在页面加载后调用调整大小函数,因为 window 在页面加载后不会自行调整大小。
代码
index.html
<!DOCTYPE html>
<!-- Onyr for Whosebug -->
<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 提出的那样的一些 hack 可以解决这个问题,但它永远不会适用于每个 iframe。只是在特定情况下。
无法使用 Javascript 与不同来源的 iFrame 交互以获取其大小;唯一的方法是使用 window.postMessage
并将 targetOrigin
设置为您的域或来自 iFrame 源的通配符 *
。您可以代理不同源站点的内容并使用 srcdoc
,但这被认为是 hack,它不适用于 SPA 和许多其他更动态的页面。
同源 iFrame 大小
假设我们有两个同源 iFrame,一个是短高度,一个是固定宽度:
<!-- 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>
我们可以使用 iframe.contentWindow.document
在 load
事件上获取 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 获得正确的宽度和高度;你可以上网查here and see the screenshot here.
不同来源的 iFrame 大小 hack(不推荐)
这里描述的方法是一个 hack,如果绝对必要并且没有其他办法,应该使用它; 它对大多数动态生成的页面和 SPA 无效。该方法使用代理获取页面 HTML 源代码以绕过 CORS 策略(cors-anywhere
是创建简单 CORS 代理服务器的简单方法,它有在线演示 https://cors-anywhere.herokuapp.com
) 然后将 JS 代码注入 HTML 以使用 postMessage
并将 iFrame 的大小发送到父文档。它甚至可以处理 iFrame resize
(结合 iFrame width: 100%
)事件并将 iFrame 大小发回父级。
patchIframeHtml
:
修补 iFrame HTML 代码并注入自定义 Javascript 的函数,它将使用 postMessage
将 iFrame 大小发送到 load
和 resize
。如果 origin
参数有一个值,那么 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
:
如果设置了 useProxy
参数,则使用代理获取页面 HTML 绕过 CORS 的函数。发送大小数据时,可以有其他参数将传递给 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();
});
}
消息事件处理函数和"Same origin iFrame size".
完全一样我们现在可以通过注入自定义 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
所以我们不要让滚动条在调整大小时闪烁 ).
你可以上网查here。
再次注意这是一个hack并且应该避免;我们不能访问跨源 iFrame 文档,也不能注入任何东西。
计算 iframe 中内容的大小有很多复杂性,主要是因为 CSS 使您能够做一些可以破坏您衡量内容大小的事情。
我写了一个库来处理所有这些事情并且还可以跨域工作,你可能会发现它很有帮助。
做一件事设置 Iframe 容器一些宽度和高度设置 CSS 属性
.iframe-container{
width:100%;
min-height:600px;
max-height:100vh;
}
.iframe-container iframe{
width:100%;
height:100%;
object-fit:contain;
}