让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为项目增加更多可能性的主要内容,如果未能解决你的问题,请参考以下文章

让iframe为项目增加更多可能性

《人月神话》

包裹iframe的div与iframe存在高度差的问题解决方案

php 包裹iframe或嵌入另一个元素

iframe跨域动态设置主窗口宽高

WPF ContentControl 宽度会增加,但在包裹在 ScrollViewer 中时不会缩小