让iframe为项目增加更多可能性
Posted 恪愚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了让iframe为项目增加更多可能性相关的知识,希望对你有一定的参考价值。
最近在研究微前端。我觉得从理论上来说,iframe是微前端最理想的组合技术。使用iframe能够将一个页面内嵌到另一个页面中,并且和链接集成一样具有松散的耦合和高鲁棒性。iframe具有极强的隔离性,其中发生的一切只会影响到自身 —— 实际上目前大多数在线编辑平台都是iframe技术实现的。
iframe难题
可是慢慢地,你会发现iframe的负面效果极其糟糕,以至于足以让人忽略其高隔离和易于实现的优势:
- 性能开销:从浏览器角度来说,向页面添加一个iframe是一项开销巨大的操作。每个iframe都会创建一个新的浏览器上下文,这会导致额外的内存和CPU消耗;
- 破坏无障碍可访问性标准:iframe破坏了页面的语义话,因为它属于另一个页面。我们可以设置iframe样式使其和页面其它部分“无缝衔接”,但是屏幕阅读器并不会被我们“欺骗”;
- SEO不友好:爬虫会将使用了iframe的页面当作两个不同的页面进行索引。依然从浏览器角度来说,iframe内外的内容看起来在同一个浏览器窗口中,实际它们不在同一个文档中;
如果你打算在项目中使用不止一个的iframe,请测试足够的用例以保证它们对性能的影响。
除了上面说的,iframe还有一个致命缺陷:缺乏可靠的iframe自动高度的解决方案。
但是笔者觉得这在某些情况下是可以尝试的。现在举一个场景实例:
在微店商家营销活动设置中,有一个商品选择功能。他会弹出来一个选择框。这时候我们注意到:只有一个商品的浮层其实用不了十个商品那么高的高度。这时候我们需要“响应式”height
。
在我司“天生支持”微前端的脚手架的架构下,商品选择是作为“通用业务组件”方式实现的,你可以理解为远程组件。然后在当前项目中以iframe形式引入。
为了避免常见组件封装的一些缺点。比如:回调函数需要以v-bind
形式单独再处理、暴露方法名改动文档同步不及时等等。我们采用了之前文章中提过的“大组件调用”方式。(实际上,“通用业务组件”的概念就和笔者提的“大组件”不谋而合)
//通用组件,无敏感代码。使用时保留下面一行即可。
// from 营销team@weidian
import ModalPC from './index.js';
let ins = null;
// 初始化选择器
export function initModal(cfg)
if (ins)
// 已有实例
console.warn("已有实例化的商品选择器。"); // ignore-console
else
/**
* @description
* 对回调函数进行了包装
*/
ins = new ModalPC(
url: cfg.url,
callback: (msg) =>
let data = msg.data;
// 页面加载以后做数据传输用途
if (data.type === 'mkt-load')
ins && ins.sendMsg(
//...
);
else
cfg.callback && cfg.callback(msg);
);
export function closeModal()
if (ins)
ins.close();
ins = null;
export function getInstance()
return ins
在index.js中:
//通用组件,无敏感代码。使用时保留下面一行即可。
// from 营销team@weidian
/**
* @description
* PC模态窗组件 -
*
*/
export default class ModalPC
constructor(param)
// 基础信息
//...
this.domWrapper = null;
this.domWindow = null;
this.domIframe = null;
this.init(param);
// 初始化方法
init(param)
// 缓存配置项参数
Object.assign(this.cfg, param);
let status = this.initOption(param);
if (status)
this.initDom();
this.bindEvent(); // 页面事件绑定
this.render(); // 组件渲染
// 配置项初始化
initOption(opt)
// dom结构初始化
initDom()
// 容器初始化
let domWrapper = this.doc.createElement('div'); // 模态窗整体容器
let domWindow = this.doc.createElement('div'); // 窗口容器
let domIframe = this.doc.createElement('iframe');
domWrapper.setAttribute('style', `
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 99999;
background: rgba(0,0,0,.5);
`);
domIframe.setAttribute('width', '100%');
domIframe.setAttribute('height', '100%');
domIframe.setAttribute('frameborder', 0);
domIframe.src = this.url;
domWrapper.append(domIframe);
// 容器缓存
this.domWrapper = domWrapper;
this.domWindow = domWindow;
this.domIframe = domIframe;
close()
// 组件注销
this.win.removeEventListener('message', this.transportMsgFn)
this.doc.body.removeChild(this.domWrapper);
// 父作用域向iframe中传值
sendMsg(msg) this.domIframe.contentWindow.postMessage(msg, "*")
//...
可以看到,我们是用一个div包裹了iframe,在iframe中又是一个div包裹整个元素。那我们是不是可以通过控制这两个“外层元素”里任意一个去控制里面的iframe呢?
我认为,最外层的元素(iframe也好、div也好)应该具备一个最大值max-height
,然后有一个动态style去根据内容展示适当的高度:
// 设置列表的高度
_setListHeight()
if (this.listHeight) return;
this.$nextTick(() =>
let _dom = this.$refs.itemListWrapper; //需要动态高度的元素
this.listHeight = Math.floor(_dom.clientHeight);
);
,
这段代码在获取商品列表数据后调用。
iframe通信
如果你用iframe构建微前端应用。那必然首要考虑iframe和页面的通信(数据传递)。
监听事件:
import MessageType from "./message-type";
/**
* 主应用,
*/
class MainApp
constructor()
this.registerEvents();
// 注册事件
registerEvents()
window.addEventListener("message", (e) =>
try
const type, data = e.data;
const arg = data, originEvent: e ;
if (type === MessageType.CHECK_COOKIE)
app.checkCookie(arg);
catch (err)
console.error("主应用接收到消息失败", err);
);
let app = null;
const start = ( onCheckCookie ) =>
app = new MainApp();
app.checkCookie = onCheckCookie;
;
export default
start,
;
// message-type.js
const MESSAGE_TYPE =
CHECK_COOKIE: "CHECK_COOKIE", // 验证 cookie
;
export default MESSAGE_TYPE;
通知事件:
import MessageType from "./message-type";
let _targetOrigin = "*";
const setup = ( targetOrigin ) =>
_targetOrigin = targetOrigin;
;
// 通知事件
const notify = (type, data) =>
top.postMessage(
type,
data,
info: //单独拿出来
data.version,
data.name,
,
,
_targetOrigin
);
;
// 验证 cookie 是否过期
const checkCookie = (data) =>
notify(MessageType.CHECK_COOKIE, data);
;
// 是否 iframe
const isIframe = () =>
return window.top !== window;
;
export default
setup,
notify,
checkCookie,
isIframe,
;
// index.js
export default as MainApp from '监听事件文件路径';
export default as MicroApp from '通知事件文件路径';
以上是关于让iframe为项目增加更多可能性的主要内容,如果未能解决你的问题,请参考以下文章