在 vue 应用程序中,有时候需要使用 iframe
嵌入其它网站的内容,并希望可以缓存 iframe
的状态。但如果被链接的源(origin) 与当前的 origin
不同时,iframe
就会发生跨域加载,这个时候,就不能在当前程序中获取 iframe 中的内容,包括
src,从而无法获取 iframe 中的状态,导致无法在初始化 iframe
时对状态进行恢复。
可以按以下方法来实现对 iframe 的缓存:
尝试过的方法
使用 keep-alive
KeepAlive 缓存的是 vnode 节点,vnode
上面会有对应的真实DOM。组件“销毁”时,会将真实 DOM
移动到“隐藏容器”中,组件重新“渲染”时会从 vnode 上取到真实
DOM,再重新插入到页面中。这样对普通元素是没有影响的,但是 iframe
很特别,当其插入到页面时会重新加载,这是浏览器特性,与 Vue
无关。
保存 iframe 实例,当组件挂载后添加到对应位置
失败的原因与第 1 点相同,当其重新插入到页面时会重新加载。
解决方案
实现步骤
思路:路由第一次加载时将 iframe
渲染到页面中,路由切换时通过 v-show
改变显/隐。
- 在路由中,将 component 赋值为一个空组件
index.vue
1 2 3 4 5
| { path: "/chathub", name: "chathub", component: ()=>import("@/chathub/index.vue") },
|
index.js 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| <template> <div class="iframe-example-index row justify-center"> <div class="column justify-center text-h6 text-secondary">加载中...</div> </div> </template>
<script> import { onMounted, ref } from 'vue' import store from '@/store/index' import { useRoute } from '@/compositions/vueRouter'
export default { name: "iframeContainer", setup() { const src = ref("www.baidu.com")
function getIframeElement() { const iframeElement = document.getElementById('iframe-example-body') return iframeElement }
async function restoreSrc() { const iframeElement = getIframeElement() if (!iframeElement) return
// 设置值 iframeElement.setAttribute('src', src.value) }
function initIFrame(force = false) { if (!force && iframeSetting.initialized) return
console.log('开始创建 iframe')
// 初始化 iframe const iframeTemp = document.createElement('iframe') iframeTemp.setAttribute('id', 'iframe-example-body') iframeTemp.setAttribute('width', '100%') iframeTemp.setAttribute('height', '100%') const container = document.getElementById('iframe-example-container') // 判断是否有 children,有的话,移除 if (container.children.length > 0) { container.removeChild(container.children[0]) } container.appendChild(iframeTemp) iframeTemp.setAttribute('src', src.value) }
onMounted(async () => { // 修改 iframe getIframeElement()
// 创建一个 iframe,保存到 iframe-example-container children 中 initIFrame()
// 从 url 中获取 src await restoreSrc() })
return { src } }, } </script>
.iframe-example-index { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; }
|
- 在 router-view 处,加载一个 container 容器组件,通过
v-if
、 v-show
来控制显示隐藏
1 2 3 4 5 6 7
| <Container v-if="iframeVisited" v-show="isIFramePageVisible"></Container>
<router-view v-slot="{ Component }"> <keep-alive :include="keepAliveList"> <component :is="Component"></component> </keep-alive> </router-view>
|
container.js 代码:
1 2 3 4
| <template> <div id="iframe-example-container"> </div> </template>
|
- 通过监听路由path的变化,改变 ChatHub 的显/隐,间接控制 iframe
的显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const isIFramePageVisible = ref(false) // 这里是个优化,想的是只有页面访问过该路由才渲染,没访问过就不渲染该组件 const iframeVisited = ref(false)
watch( () => routes.path, (value) => { if (value === '/iframe') { iframeVisited.value = true isIFramePageVisible.value = true } else { isIFramePageVisible.value = false } }, { immediate: true } )
|
注意事项
iframe 只有处于显示时,才会加载 iframe 里面的内容。因此,必须保证
iframe 不在后台实例化,可以通过以下方式实现:
- 通过
v-if
来控制初始化加载
- 在
index.vue
第一次加载时,使用 js 初始化
iframe,然后添加到 ChatHub
作为其子元素。
如果后台先实例了 iframe 后,会导致从其它页面切换到 /chathub
页面时,无法正常加载 iframe 里面的内容,并影响性能。
参考
本文参考以下文章,在此报以诚挚谢意!
vue缓存iframe的解决方法
Vue
KeepAlive 为什么不能缓存 iframe