You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
By clicking “Sign up for GitHub”, you agree to our
terms of service
and
privacy statement
. We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account
最近项目中需要使用到内嵌网页,最开始本来是考虑使用
webview
标签,但是 👉
Electron 的
webview
标签基于
Chromium webview </0>
,后者正在经历巨大的架构变化。 这将影响
webview
的稳定性,包括呈现、导航和事件路由。 我们目前建议不使用
webview
标签,并考虑其他替代方案,如
iframe
、Electron 的
BrowserView
或完全避免嵌入内容的体系结构。
而
BrowserView
则是由 Electron 主进程控制,调用成本较高,并且是置于窗口的最顶层,会覆盖父窗口的 tooltip,效果不理想,最后就只有
iframe
可供使用。但是使用过程中发现,当路由切换后,iframe 会重载,里面的所有状态都会重置。首先想到 vue-router 的问题,当切换回路由时组件重新渲染,导致 iframe 重载。但项目由于要保存路由状态,使用了
keep-alive
。
<keep-alive>
本质上它就是去缓存已经创建过的
vnode
,而缓存的
vnode
对象也会持有 DOM,因此 DOM 并不会销毁。既然 DOM 还是原来的 DOM,那为什么
iframe
中的内容会重载呢 😢。试着在 vue-router 的 issue 下查找关于 iframe 的问题,基本都是
route with iframe changes the iframe then the page when hitting back
之类的问题,没有较好的解决方案。
最后在看了下
MDN 上关于
iframe
的介绍
:
HTML 内联框架元素 (
<iframe>
)
表示嵌套的
browsing context
。它能够将另一个 HTML 页面嵌入到当前页面中。
每个嵌入的浏览上下文(embedded browsing context)都有自己的会话历史记录 (session history) 和
DOM 树
。包含嵌入内容的浏览上下文称为
父级浏览上下文
。顶级浏览上下文(没有父级)通常是由
Window
对象表示的浏览器窗口。
莫非 iframe 的加载和所在 DOM 树也有关系?首先看看 iframe 的 load 时机:
(() => {
const el = document.createElement('iframe');
el.onload = () => console.log('el load', el);
el.src = 'http://nodejs.cn/';
setTimeout(() => {
document.documentElement.appendChild(el);
console.log('body append el -> ', el);
}, 5000);
})();
可以看到在
iframe
被插入
document
之后才触发 load 事件,而同为
可替换元素
的 img 元素则是在更新
src
属性后就会触发 load 事件,可能就是因为这个
browsing context
和所在 DOM 树有关。
现在来模拟一下 vue-router 中使用
keep-alive
的情况:
<button id="remove">remove</button> <button id="append">append</button>
<iframe
id="iframe"
src="http://nodejs.cn/"
width="100%"
height="600px"
frameborder="0"
></iframe>
</main>
<script>
const $iframe = document.querySelector('#iframe');
const $remove = document.querySelector('#remove');
const $append = document.querySelector('#append');
const $main = $iframe.parentNode;
$remove.onclick = () => $iframe.remove();
$append.onclick = () => $main.appendChild($iframe);
$iframe.addEventListener('load', (e) => {
console.log('load ->', e);
});
</script>
在离开路由时,iframe 会从 DOM 树中移除,但依然会保留在 vNode 中,不会被垃圾回收机制回收掉。这里点击 remove 按钮移除并保存在变量
$iframe
中。
然后进入路由后,通过 vNode 的缓存重新将元素节点插回 DOM 树中。这里点击 append 按钮插到
<main>
节点下。
可以看到在进入页面后 load 一次,执行以上操作之后又 load 了一次。
我们 remove 操作前后在控制台输入
$iframe.contentWindow
,
发现 iframe 在离开 DOM 树后 window 对象为 null,可以说明子窗口已被销毁,通过对比也可以发现前后两个 window 并不相同。
回到最开始的问题,如何在 vue-router 下使用 iframe 并在切换路由时不重载?
首先
keep-alive
是必须使用的,保证组件状态不变。iframe 元素加载后不能脱离 DOM 树,这里考虑在将 iframe 所在的节点移动到
document.documentElement
下,再去通过 css 修改元素的位置(这里我使用
position: fixed;
控制位置,
z-index
控制显示层级)以及显示状态。
// 使用 v-show="active" 来控制 this.$refs.xxx 的显示
export default {
data() {
return {
active: true,
activated() {
this.active = true;
deactivated() {
this.active = false;
mounted() {
document.documentElement.appendChild(this.$refs.xxx);
beforeDestroy() {
document.documentElement.removeChild(this.$refs.xxx);