需求
有时候我们有这样的需求:我们需要挂载一个链接地址内容到本页面。后台只会返给你一个 url 链接,此时能确定的实现方案就是利用 iframe 来渲染这个链接。比如这样:
<iframe width="100%" height="500px" frameBorder="0" src="url" />
这样就可以解决问题。
高度问题
我们知道,iframe 默认高度为150,我们经常需要自定义高度。一般来说,高度来源就两种方式:
- 子页面提供
- 父页面自己获取
当然,子页面内容如果相对固定,也可以直接写死,这里就不讨论了。
比较简单的方式是让父页面自己获取,我们可以利用 onLoad 事件来实现:
function adjustIframeHeight() {
if (this.$refs.iframeRef) {
this.$refs.iframeRef.style.height = this.$refs.iframeRef.contentWindow.document.body.scrollHeight + 'px';
}
}
在元素中,这样写:
<iframe src="url" width="100%" frameBorder="0" @load="adjustIframeHeight" ref="iframeRef" />
此时,就可以让 iframe 在加载后自动计算高度,然后让其自动匹配内部高度。
好了么?其实这里还有一个问题,就是当子页面的 url 与当前页面不同源时,浏览器出于安全原因,不允许父页面获取子页面的 document
:
这也就无法获取到子页面的正确高度。此时,就需要子页面主动提供。如果子页面也是可控的,那么修改一下,让其内部加载完毕后发送一个 postMessage
,父页面就可以正常接收到。这个方式也是通常使用 iframe 解决跨域的方式之一,这里就不赘述了。有了子页面提供的高度,那么父元素直接拿来用就好了,不会出现任何问题。
但是!!!如果我们需要挂的是外网地址,页面不是我们的,我们无法控制,但同样有跨域的问题,获取不到高度,页面就不能自适应子页面,而且子页面链接还可能不同,我们不可能为每一个子页面出一套对应的高度样式。
没有办法了么?前端解决不了的,让后端来解决。
使用代理
针对上面 buff 叠满的情况,我们只有一条路走,那就是父页面自主获取子页面高度,但是因为跨域问题,导致我们无法获取到子页面的docuemnt,那我们就只能让它们同源~!
让后端同学代理这个地址,这样,我们的链接就同源了。这里以 node 为例:
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
app.use(express.static('public'));
app.get('/proxy', async (req, res) => {
const url = req.query.url;
if (!url) {
return res.status(400).send('URL is required');
}
try {
const response = await axios.get(url);
res.send(response.data);
} catch (error) {
res.status(500).send('Error fetching the URL');
}
});
app.listen(port, () => {
console.log(`Proxy server listening at http://localhost:${port}`);
});
让服务器接收这个链接,然后将内容完全返回。在前端,我们只需要将这个 url 拼接到这个服务地址的参数中即可:
<iframe src="http://localhost:8080/proxy?url=xxx" width="100%" frameBorder="0" />
此时,我们再用 onLoad 事件监听,就可以轻松获取到 contentWindow
中的 document,此时,我们就可以任意调整 iframe 元素的高度,以适配内部子页面的高度,从而解决问题。
放开思路
还有一种其他途径,我们可以直接放弃使用 iframe,通过直接获取子页面内容,自行截取需要的内容,比如 <body>...</body>
部分,将其适当修改后,添加到我们的页面中:
// 获取 url 页面 html
fetch('url', { credentials: "include", crossDomain: true}).then(res => return res.text()).then(content => console.log(content));
获取到完整的页面信息后,通过各种方式,比如正则匹配,可以轻松获取到目标元素(这里就不展开怎么获取了),就可以利用渲染机制,将文本渲染到页面中,比如 vue:
<div v-html="content" />
我们可以在这个 div 中添加任意样式。
摒弃 iframe
我一直竭尽所能摒弃 iframe。原因有三:
- 样式问题,比如上面出现的 height。当然,必须要承认,iframe 在样式隔离上有一定优势
- 内容问题。它是独立渲染一个页面,当内部有一些 js 逻辑,比如强制跳转的时候,父页面无法预期和控制
- 交互问题。父子页面无法便利的做到类似滚动交互、点击交互,这些都需要利用 postMessage 传递参数来模拟实现,前提是子页面受我们控。
我个人理解,iframe 就不应该出现在 h5 上,它是一个老旧的玩意,曾经风光一时,但仍然无法避免它现在的短板。
文章评论