探索 iframe 花式用法

416 阅读3分钟

1、机遇

开局一张图,有图有真相!

img-20230211165824.png

  • 最近接手了一个 React 项目,这是一个由 create-react-app 创建的项目,每次代码保存触发热更新时就会产生烦人的 iframe ,它的层级非常高,导致我无法审查元素,就是上面这玩意儿。

  • 这是怎么回事呢?又要如何干掉呢?

  • stop! stop! stop! 打住!打住!打住!今天的主角不是要分析问题是如何产生的以及如何解决它。

2、发现猫腻

  • 注意看上面的截图,你会惊讶的发现它是没有 src 属性的。其他用法与之无异。

  • 那么问题来了:没有 src 属性它是如何加载内部内容的?

  • 于是, MDN 了一波:发现了两个重要的 API:

    • HTMLIFrameElement.contentDocument:返回一个 Document,该内联 frame 嵌套的浏览上下文中活跃的 document 对象。
    • HTMLIFrameElement.contentWindow:返回一个 WindowProxy,该嵌套的浏览上下文中的 window 代理。
  • 也就是说 这两个方法可以拿到 iframe 的 documentwindow,与我们日常使用的 documentwindow 没有差异。也就是说我们可以手动设置 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)
    `;
    
  • 延迟一秒后,视频播放出来了,大工告成!可喜可贺!

img-20230211175800.png

  • 先收一下激动的心,颤抖的手。我们还忽略了一点。注意看这个:

img-20230211180026.png

  • 就是这个 alert,你有没有看出什么?你看到的是 localhost:3000 显示,所以弹窗是在父级弹出来,所以你想到什么了吗?!

6、iframe 正确使用姿势

特点:
(1)iframe 有天然的沙箱隔离机制
(2)嵌入webCompont 组件,解决模态弹窗不会锁死出不来的问题
(3)微前端解决方案?指引:无界
(4)iframe 通信机制,可以运行一个单独线程?类似 web worker

更多脑洞,期待您的开启!GO GO GO !!!