Code前端首页关于Code前端联系我们

Vue2 里咋把 iframe 嵌入页面?

terry 9小时前 阅读数 8 #Vue
文章标签 Vue2 iframe

不少做前端开发的同学在 Vue2 项目里碰到 iframe 时,总会犯难——咋把 iframe 嵌入页面?父子组件咋传数据?自适应高度咋搞?还有性能和兼容性这些坑咋避?这篇用问答形式,把 Vue2 和 iframe 打交道的常见问题掰碎了讲,不管你是刚接触的新手,还是想优化现有代码的老鸟,看完多少能解决点实际问题~
先从最基础的嵌入说起,Vue2 项目里,iframe 就是个普通 HTML 标签,直接在 template 里写就行,但要是 src 是动态的,得用 v - bind 或者冒号语法绑定,举个实际开发里的例子:

<template>
  <div class="iframe - wrapper">
    <iframe 
      :src="iframeSrc" 
      frameborder="0" 
      width="100%" 
      height="600" 
      @load="handleIframeLoad"
    />
  </div>
</template>
<script>
export default {
  data() {
    return {
      iframeSrc: 'https://你的域名.com/page' // 替换成实际要嵌入的地址
    }
  },
  methods: {
    handleIframeLoad() {
      console.log('iframe 加载完成啦~');
      // 加载完成后才能操作内部内容,后面通信部分会详细讲
    }
  }
}
</script>
<style scoped>
.iframe - wrapper {
  /* 给 iframe 包个容器,方便控制样式 */
  border: 1px solid #eee;
}
</style>

这里得注意同源策略这事儿!要是嵌入的网站和当前 Vue 项目域名不一样,浏览器会限制很多操作(比如后面要讲的父子通信、修改内部 DOM),要是你能控制嵌入的页面(比如是公司内部系统),尽量让域名保持一致,或者用代理配置解决跨域;要是第三方网站(比如嵌入某新闻门户),那只能乖乖遵守浏览器规则,很多操作会受限。

iframe 加载是“异步”的,Vue 的 mounted 生命周期钩子执行时,iframe 可能还没完全加载好,所以如果要操作 iframe 内部的 DOM 元素,得等 @load 事件触发后再处理,就像上面代码里的 handleIframeLoad 那样~

Vue2 父组件和 iframe 子页面咋通信?

很多场景下,父组件要给 iframe 传数据(比如用户登录态),iframe 里的页面也要给父组件反馈(比如表单提交状态),这里分同域跨域两种情况讲,因为浏览器对这两种场景的限制不一样~

同域通信(父 ↔ 子域名相同)

要是父组件和 iframe 里的页面都是 xxx.com 下的,通信就简单些,核心是用 postMessage 配合 contentWindow

父组件给 iframe 发消息
先给 iframe 加个 ref,方便拿到它的 contentWindow(也就是 iframe 里页面的 window 对象)。

<template>
  <iframe 
    ref="myIframe" 
    :src="sameDomainSrc" 
    @load="sendMsgToIframe"
  />
</template>
<script>
export default {
  data() {
    return {
      sameDomainSrc: 'https://xxx.com/child - page'
    }
  },
  methods: {
    sendMsgToIframe() {
      const iframeWin = this.$refs.myIframe.contentWindow;
      // 发消息给 iframe,第二个参数是目标域名(必须和 src 一致,否则被拦截)
      iframeWin.postMessage(
        { type: 'parentMsg', data: '父组件给你带了个消息~' }, 
        'https://xxx.com'
      );
    }
  }
}
</script>

iframe 里的子页面(假设也是 Vue 项目),得监听 message 事件,还要验证消息来源(防止恶意网站冒充):

// 子页面的 mounted 钩子或者单独的 js 文件里
window.addEventListener('message', (event) => {
  // 验证消息来自父组件的域名
  if (event.origin === 'https://xxx.com') {
    console.log('收到父组件消息:', event.data);
    // 这里可以根据 event.data.type 做不同逻辑,比如渲染数据、弹提示
  }
});

子页面给父组件发消息
子页面里直接用 window.parent(父组件的 window 对象)发 postMessage

// 子页面的某个按钮点击事件里
window.parent.postMessage(
  { type: 'childReply', data: '子页面处理完啦~' }, 
  'https://xxx.com'
);

父组件得监听 message 事件,同样验证来源:

<template>
  <!-- 其他代码 -->
</template>
<script>
export default {
  mounted() {
    // 组件加载后监听 message
    window.addEventListener('message', this.handleChildMsg);
  },
  beforeDestroy() {
    // 组件销毁前移除监听,防止内存泄漏
    window.removeEventListener('message', this.handleChildMsg);
  },
  methods: {
    handleChildMsg(event) {
      if (event.origin === 'https://xxx.com') {
        console.log('收到子页面消息:', event.data);
        // 处理子页面的反馈,比如更新父组件状态、跳转路由
      }
    }
  }
}
</script>

跨域通信(父 ↔ 子域名不同)

要是嵌入的是第三方网站(other.com),域名和父组件不一样,那只能靠 postMessage,但要严格验证 origin,否则有安全风险。

父给子发消息
逻辑和同域类似,但 postMessagetargetOrigin 要写对方的域名(比如第三方是 https://other.com):

<template>
  <iframe 
    ref="crossIframe" 
    :src="crossDomainSrc" 
    @load="sendCrossMsg"
  />
</template>
<script>
export default {
  data() {
    return {
      crossDomainSrc: 'https://other.com/third - page'
    }
  },
  methods: {
    sendCrossMsg() {
      const iframeWin = this.$refs.crossIframe.contentWindow;
      iframeWin.postMessage(
        { type: 'crossMsg', data: '跨域消息请注意~' }, 
        'https://other.com' // 必须写对方域名,不能用 *(不安全)
      );
    }
  }
}
</script>

第三方页面(子页面)监听时,event.origin 要等于父组件的域名:

window.addEventListener('message', (event) => {
  if (event.origin === 'https://父组件域名.com') {
    console.log('收到跨域消息:', event.data);
  }
});

子给父发消息同理,子页面里:

window.parent.postMessage(
  { type: 'crossReply', data: '跨域回复来啦~' }, 
  'https://父组件域名.com'
);

父组件监听时,验证 event.origin 是子页面的域名,再处理消息~

这里额外提醒:postMessagedata 尽量用简单数据类型(比如字符串、数字),如果传对象,跨域场景下某些旧浏览器可能解析有问题,稳妥点可以先 JSON.stringify 再传,接收端再 JSON.parse

iframe 高度自适应,Vue2 咋处理?

默认 iframe 有滚动条,用户体验差,得让高度“跟着内容走”,这也分同域和跨域场景~

同域下的高度自适应

如果父组件和 iframe 同域,父组件能直接拿到 iframe 内部的文档高度,动态设置 iframe 的 height

步骤 1:监听 iframe 加载,获取内容高度
给 iframe 加 ref@load 事件,加载完成后计算高度:

<template>
  <iframe 
    ref="autoIframe" 
    :src="sameDomainSrc" 
    @load="adjustHeight"
    frameborder="0"
    width="100%"
  />
</template>
<script>
export default {
  data() {
    return {
      sameDomainSrc: 'https://xxx.com/long - page' // 假设这个页面内容很长
    }
  },
  methods: {
    adjustHeight() {
      const iframe = this.$refs.autoIframe;
      if (iframe) {
        // 获取 iframe 内部文档的高度(兼容不同浏览器)
        const contentDoc = iframe.contentDocument || iframe.contentWindow.document;
        const height = contentDoc.body.scrollHeight || contentDoc.documentElement.scrollHeight;
        iframe.style.height = height + 'px';
      }
    }
  }
}
</script>

步骤 2:处理 iframe 内容动态变化
要是 iframe 里的页面有异步内容(比如加载图片、接口数据),加载完成后高度会变,这时候得让子页面主动通知父页面更新高度。

子页面(同域)里,在内容变化的关键节点(mounted、数据请求完成后)发消息:

// 子页面的 mounted 钩子
mounted() {
  this.sendHeightToParent();
  // 假设还有异步请求,请求完成后再发
  this.$axios.get('/api/data').then(() => {
    this.sendHeightToParent();
  });
},
methods: {
  sendHeightToParent() {
    const height = document.body.scrollHeight;
    window.parent.postMessage(
      { type: 'updateHeight', height }, 
      'https://xxx.com'
    );
  }
}

父组件监听这个消息,更新 iframe 高度:

<template>
  <!-- 其他代码 -->
</template>
<script>
export default {
  mounted() {
    window.addEventListener('message', this.handleHeightUpdate);
  },
  beforeDestroy() {
    window.removeEventListener('message', this.handleHeightUpdate);
  },
  methods: {
    handleHeightUpdate(event) {
      if (event.data.type === 'updateHeight') {
        const iframe = this.$refs.autoIframe;
        if (iframe) {
          iframe.style.height = event.data.height + 'px';
        }
      }
    }
  }
}
</script>

跨域下的高度自适应

要是 iframe 是跨域的,父组件拿不到子页面的 DOM,只能让子页面主动发高度给父页面(前提是子页面你能改代码,比如是公司合作方的页面)。

子页面(跨域)里:

window.addEventListener('load', () => {
  const height = document.body.scrollHeight;
  window.parent.postMessage(
    { type: 'crossHeight', height }, 
    'https://父组件域名.com'
  );
  // 还要监听窗口大小变化、内容展开等事件,及时发新高度
  window.addEventListener('resize', () => {
    const newHeight = document.body.scrollHeight;
    window.parent.postMessage(
      { type: 'crossHeight', newHeight }, 
      'https://父组件域名.com'
    );
  });
});

父组件同样监听 message,拿到高度后设置 iframe 高度:

<template>
  <!-- 其他代码 -->
</template>
<script>
export default {
  methods: {
    handleCrossHeight(event) {
      if (event.data.type === 'crossHeight') {
        const iframe = this.$refs.crossIframe;
        if (iframe) {
          iframe.style.height = event.data.height + 'px';
        }
      }
    }
  }
  // 其他生命周期钩子和监听逻辑和之前类似
}
</script>

要是第三方页面不能改代码(比如嵌入某不可控的外部页面),那只能妥协——要么留滚动条,要么预估一个高度(height: 800px),但体验肯定不如自适应好,这也是 iframe 跨域的无奈之处~

Vue2 里用 iframe 有啥性能坑?咋优化?

很多同学做完功能后,发现页面越来越卡,一查是 iframe 在“偷偷搞事情”,常见性能问题和优化思路如下:

iframe 阻塞页面加载

iframe 加载时会抢占主页面的网络资源和渲染线程,尤其是多个 iframe 同时加载,主页面会变卡。

优化:懒加载 iframe
不要一进页面就加载所有 iframe,等用户需要的时候再加载(比如点击 Tab、滚动到可视区域),用 v - if 控制加载时机:

<template>
  <div>
    <button @click="showIframe = true">点我加载 iframe</button>
    <iframe v - if="showIframe" :src="iframeSrc" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      showIframe: false,
      iframeSrc: 'https://xxx.com/heavy - page'
    }
  }
}
</script>

这样用户不点击就不加载,减少初始加载压力~

内存泄漏

iframe 即使被隐藏(比如用 v - show),它的进程还在后台运行,时间久了会内存泄漏。

优化:销毁时主动清理资源
在组件 beforeDestroy 钩子(或路由离开时),主动清空 iframe 的 src 并移除 DOM:

<template>
  <iframe ref="leakIframe" :src="iframeSrc" />
</template>
<script>
export default {
  data() {
    return {
      iframeSrc: 'https://xxx.com'
    }
  },
  beforeDestroy() {
    const iframe = this.$refs.leakIframe;
    if (iframe) {
      iframe.src = 'about:blank'; // 清空 src,释放资源
      iframe.parentNode.removeChild(iframe); // 从 DOM 移除
    }
  }
}
</script>

重复渲染/加载

iframe 的 src 不变,Vue 的重新渲染可能会重复加载 iframe,导致性能浪费。

优化:给 iframe 加唯一 key
:key 绑定 src(或其他唯一标识),只有 src 变化时才重新加载:

<iframe :src="iframeSrc" :key="iframeSrc" />

Vue2 中 iframe 常见 Bug 咋排查?

开发时碰到 iframe 不显示、通信失败、样式乱套这些问题,别慌,按步骤排查:

iframe 不显示

  • 先看控制台:有没有跨域报错(Refused to display ... in a frame)?如果有,回到“同源策略”部分解决。
  • 检查 src:是不是拼写错了?协议对不对(比如主页面是 https,iframe 用了 http,会被浏览器拦截)?
  • 检查样式:iframe 默认有边框,且宽度高度可能很小,设置 frameborder="0"width="100%"height 给个初始值(600px)试试。

通信失败

  • 验证 originpostMessagetargetOriginevent.origin 是不是一致?尤其是跨域时,域名多一个斜杠、少个 www 都会失败。
  • 检查事件监听:父组件的 message 监听是不是在 mounted 里加了?子页面的 message 监听是不是在 load 之后加的(防止 iframe 还没加载好就监听)?
  • 测试简单数据:先传字符串试试,排除对象序列化的兼容问题。

样式冲突/修改无效

  • 同域情况下,想修改 iframe 内部样式,得用 contentDocument 操作:
<template>
  <iframe ref="styleIframe" :src="sameDomainSrc" @load="changeIframeStyle" />
</template>
<script>
export default {
  methods: {
    changeIframeStyle() {
      const iframeDoc = this.$refs.styleIframe.contentDocument;
      iframeDoc.body.style.backgroundColor = 'lightblue';
      iframeDoc.querySelector('.title').style.color = 'red';
    }
  }
}
</script>
  • 跨域情况下,父组件没权限改 iframe 内部样式,只能让 iframe 自己的页面改。

SEO 问题

iframe 里的内容搜索引擎抓不到,如果是公司官网的核心内容(比如产品介绍),别用 iframe!改用组件嵌入、服务端渲染(SSR)或者静态化页面。

最后总结下:Vue2 里用 iframe,核心是搞懂嵌入方式、通信规则、自适应逻辑、性能优化这几块,同域和跨域场景差异大,得根据实际需求选方案;遇到问题别慌,从控制台报错、同源策略、事件监听这些基础点排查,大部分坑都能解决~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门