1、机遇
开局一张图,有图有真相!
-
最近接手了一个 React 项目,这是一个由
create-react-app创建的项目,每次代码保存触发热更新时就会产生烦人的 iframe ,它的层级非常高,导致我无法审查元素,就是上面这玩意儿。 -
这是怎么回事呢?又要如何干掉呢?
- 这应该是脚手架产生的 bug, 参考文章:react项目热更新自动插入了iframe
-
stop! stop! stop! 打住!打住!打住!今天的主角不是要分析问题是如何产生的以及如何解决它。
2、发现猫腻
-
注意看上面的截图,你会惊讶的发现它是没有
src属性的。其他用法与之无异。 -
那么问题来了:没有
src属性它是如何加载内部内容的? -
于是, MDN 了一波:发现了两个重要的 API:
HTMLIFrameElement.contentDocument:返回一个 Document,该内联 frame 嵌套的浏览上下文中活跃的 document 对象。HTMLIFrameElement.contentWindow:返回一个 WindowProxy,该嵌套的浏览上下文中的 window 代理。
-
也就是说 这两个方法可以拿到 iframe 的
document和window,与我们日常使用的document和window没有差异。也就是说我们可以手动设置 iframe 的内容。
3、无限遐想
-
我们知道, iframe 加载第三方的链接会产生跨域问题,那是不是我手动设置第三方的
html内容就达到了相同的效果? -
于是乎,我拿百度开刀。在百度官网抠下了 html 内容:以下是尝试代码:
<iframe width="100%" frameborder="0"></iframe>const iframe = document.querySelector("iframe"); iframe.contentDocument.head.innerHTML = ` <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <title>百度一下,你就知道</title> <style>...(省略)</style> `; iframe.contentDocument.body.innerHTML = ` <a id="aging-total-page" role="pagedescription" aria-label="欢迎进入 百度一下,你就知道,盲人用户进入读屏幕模式请按快捷键Ctrl加Alt加R;阅读详细操作说明请按快捷键Ctrl加Alt加问号键。" tabindex="0" href="javascript:void(0)"></a><script> ...(省略) `; -
发现控制台报错了,错误原因是复制的内容太长了,存在语法解析错误。
4、解决问题
- 着手一个简单的 html 页面尝试:
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js"></script> </head> <body> <div id="dplayer"></div> <script> const url = "https://open.ys7.com/v3/openlive/G29169236_21_1.m3u8?expire=1698646785&id=508646539304902656&t=b818d54ac81b7eb5622a8260e4484aa0c11843c72f73a35289702cd1411c51f1&ev=100"; const dp = new DPlayer({ container: document.getElementById('dplayer'), live: true, video: { url: url, type: 'hls', }, }); </script> </body> </html> - 这是一个简单的视频播放 demo,根据前面的操作:
const iframe = document.querySelector("iframe"); iframe.contentDocument.head.innerHTML = ` <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js"></script> `; iframe.contentDocument.body.innerHTML = ` <div id="dplayer"></div> <script> const url = "https://open.ys7.com/v3/openlive/G29169236_21_1.m3u8?expire=1698646785&id=508646539304902656&t=b818d54ac81b7eb5622a8260e4484aa0c11843c72f73a35289702cd1411c51f1&ev=100"; const dp = new DPlayer({ container: document.getElementById('dplayer'), live: true, video: { url: url, type: 'hls', }, }); </script> `; - 哈哈,小有成就!你会愉快的发现 dom 写进去了,但是视频为什么没加载,没播放出来呢?
- 盲猜一波:是不是
js脚本没有执行? 那我们添加alert(111)试一下。iframe.contentDocument.body.innerHTML = ` <div id="dplayer"></div> <script> alert(111) const url = "https://open.ys7.com/v3/openlive/G29169236_21_1.m3u8?expire=1698646785&id=508646539304902656&t=b818d54ac81b7eb5622a8260e4484aa0c11843c72f73a35289702cd1411c51f1&ev=100"; const dp = new DPlayer({ container: document.getElementById('dplayer'), live: true, video: { url: url, type: 'hls', }, }); </script> `; - 果不其然,怎么办呢,挺急的?我们百度一波:见:iframe内部添加的脚本不执行。
- 好家伙!脚本必须动态插入才会执行!
- 来吧,改造一波:
const iframe = document.querySelector("iframe"); const script = iframe.contentDocument.createElement("script"); const script2 = iframe.contentDocument.createElement("script"); const script3 = iframe.contentDocument.createElement("script"); iframe.contentDocument.head.innerHTML = ` <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <title>Document</title> `; script.src = "https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js"; script2.src = "https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js"; iframe.contentDocument.head.append(script); iframe.contentDocument.head.append(script2); script3.innerHTML = ` alert(111) const url = "https://open.ys7.com/v3/openlive/E93888992_10_1.m3u8?expire=1700287684&id=515528970054152192&t=a17001f430caf70aae751560ab6bfb44419ff70848f6354c2cc04b04deae6592&ev=100"; const dp = new DPlayer({ container: document.getElementById('dplayer'), live: true, video: { url: url, type: 'hls', }, }); `; iframe.contentDocument.body.innerHTML = '<div id="dplayer">视频播放器</div>'; iframe.contentDocument.body.append(script3);
5、收获喜悦
- 好了,到了见证奇迹的一刻了,
alert它弹出来了。脚本执行了,开心的起飞了吧! - 等等,好像还有点问题,视频为啥没播出来?我们看看控制台,发现
new DPlayer is not undefined? 纳尼? - 打印一下
console.log(document, window)呗!看控制台,发现window下面是有DPlayer的。说明了啥? - 说明了执行了
new DPlayer的时候,script 脚本内容还没加载完。改造一下呗:script3.innerHTML = ` const url = "https://open.ys7.com/v3/openlive/E93888992_10_1.m3u8?expire=1700287684&id=515528970054152192&t=a17001f430caf70aae751560ab6bfb44419ff70848f6354c2cc04b04deae6592&ev=100"; setTimeout(() => { const dp = new DPlayer({ container: document.getElementById('dplayer'), live: true, video: { url: url, type: 'hls', }, }); }, 1000) `; - 延迟一秒后,视频播放出来了,大工告成!可喜可贺!
- 先收一下激动的心,颤抖的手。我们还忽略了一点。注意看这个:
- 就是这个
alert,你有没有看出什么?你看到的是localhost:3000 显示,所以弹窗是在父级弹出来,所以你想到什么了吗?!
6、iframe 正确使用姿势
特点:
(1)iframe 有天然的沙箱隔离机制
(2)嵌入webCompont组件,解决模态弹窗不会锁死出不来的问题
(3)微前端解决方案?指引:无界
(4)iframe 通信机制,可以运行一个单独线程?类似web worker
更多脑洞,期待您的开启!GO GO GO !!!