你不了解的iframe

1,117 阅读4分钟

简介

在开发的过程中,即使在现代浏览器的中,有时还是会遇到使用iframe的时候,虽然iframe在性能和安全方面,有很多诟病,但是它还是有一些用途,能够解决一些棘手的问题。本身将会列举几个用于。

iframe是什么呢?

iframe全称inline frame。顾名思义,嵌入在一个HTML上下文中的窗口。尽管是嵌入到另一个文档中的上下文,但iframe仍然拥有属于自己的滚动条。

iframe的优缺点

优点

  1. 作为一个完全独立的窗口去运行另一个页面,而不用去担心污染。
  2. 解决加载缓慢的第三方内容如图标和广告等的加载问题
  3. 并行的加载资源文件

缺点

  1. 占用同源连接数,对于每个浏览器,都会去控制并发的同源的连接数。比如说chrome的连接数是8,firfox的连接数6。所以如果iframe的请求与页面的请求是同源的,那么就会阻塞页面的请求。
  2. iframe无法使用浏览器的前进和后退键
  3. 对SEO不友好
  4. 阻塞onload的加载

iframe的简单使用

在这里具体的iframe Api也就不一一列举了。文档的传送门>>>>

<iframe id="inlineFrameExample"
    title="Inline Frame Example"
    width="300"
    height="200"
    src="https://www.openstreetmap.org/export/embed.html?bbox=-0.004017949104309083%2C51.47612752641776%2C0.00030577182769775396%2C51.478569861898606&layer=mapnik">
</iframe>

下面才是本体,注意啦!!!

你可能不知道用途

不使用websocket进行轮询

websocket无法使用的时候,作为长轮询功能的解决方案。主要用于实现ie8、9这种早期浏览器的轮询功能。一开始在谷歌中有使用过Comet:基于 HTTP 长连接的“服务器推”技术

1. 创建服务端,新建一个接口

/* GET users listing. */
router.get('/users', function(req, res, next) {
  res.send('respond with a resource');
});

2. 编写客户端js

<div class="iframe-content" />
<script>
    // 创建一个iframe并将它隐藏
    const iframeEle = document.createElement('iframe');
    iframeEle.style = 'display: none;';
    iframeEle.name = 'poll';
    iframeEle.src="http://localhost:3000/users";
    // 监听load事件,请求完成后将会运行load事件
    iframeEle.onload = function() {
        var iframeLoc = iframeEle.contentWindow.location;
        var result = iframeEle.contentDocument;
        // 这里将会拿到响应的结果
        var text = result.getElementsByTagName('body')[0].textContent;
        console.log(text)
        // 设置定时器,实现轮询功能
        setTimeout(function() {
            iframeLoc.reload(); 
        }, 3000);
    }
    var iframeContent = document.getElementsByClassName('iframe-content')[0];
    iframeContent.appendChild(iframeEle);
</script>

保证同源,否则无法访问到iframedocument对象。

image.png

不使用formData上传文件

当我们不能使用formData上传文件时,我们可以使用iframe进行页面无刷新的上传文件。方法其实很简单,就是使用iframe代替当前页面上传,只需要将定义好的form标签的target赋值为iframename属性。

  1. 页面代码
    <form id="submit-form" name="submit-form" target="submit-iframe"  enctype="multipart/form-data" action="/upload" method="POST">
        <input type="file" name="files">
        <button type="submit" id="submit-btn">上传</button>
    </form>
    需要注意到。iframe的name和form的target属性是一致的
    <iframe style="display: none;" id="submit-iframe" name="submit-iframe"></iframe>
    <script>
        const iframeEle = document.getElementById('submit-iframe');
        // 上传完成后,可以在iframe中拿到响应的结果
        iframeEle.onload= function() {
            var iframeLoc = iframeEle.contentWindow.location;
            var resultWrapper = iframeEle.contentDocument;
            var result = resultWrapper.getElementsByTagName('body')[0].textContent;
            console.log(result);
        }
    </script>
  1. 上传接口
var express = require('express');
var multer = require('multer');
var router = express.Router();

let storage=multer.diskStorage({
    destination:(req,file,cb)=>{
        cb(null,'upload/');
    },
    filename:(req,file,cb)=>{
        cb(null,file.originalname);
    }
})

var upload = multer({
  storage
})

router.post('/upload', upload.single('files'),function(req, res, next) {
  res.send("上传完成");
})

iframe跨域解决跨域请求

iframe解决跨域通信,我们优先选择postmessage,进行父子页面的通信。只有在无法使用postmessage的情况下,我们才会选择window.name完成数据传递。

1. 创建主页面

主页面中包含了2个iframe,一个用于请求不同域的接口fetchFrame。一个用于轮询监听前者的location变化observeFrame。且主页面包含一个回调方法用于供observeFrame调用。

    <div class="iframe-content">
        <div>主页面</div>
        <!-- 同域 -->
        <iframe src="http://hsa.guahao-test.com:3000/fetch.html" name="fetch" id="fetch"></iframe>
        <!-- 不同域 -->
        <iframe src="http://tcmedev.udplat.com:8080/observe.html" name="inner" id="inner"></iframe>
    </div>
    <script>
        // 主页面用于iframe的回调函数
        window.message = function(str) {
            console.log(str);
        }
    </script>

2. 编写fetchFrame的代码

fetchFrame的页面中,可以看到代码使用setTimeout进行模拟异步请求,在回调函数中会将结果存放进window.name中,并改变location的指向。需要注意的是,我们使用的是window.name去处理iframe的数据传递。因为window.name在改变窗口地址的情况下不会改变数据且它具有2M的空间大小。但是在不同域的情况下,window.name无法被访问到,所以在赋值完成后会改变iframe的地址。

setTimeout(() => {
    // 存放数据
    window.name = new Date();
    // 改成location,使得与主页面同源
    window.location = 'http://tcmedev.udplat.com:8080/index3.html'
}, 3000);

3. 编写observeFrame的代码

observeFrame中,创建了一个定时器去轮询fetchFramewindow对象,在不同域的情况下,访问异步的iframe会抛出错误。所以代码中使用try-catch进行包裹,当fetchFramelocation改为同域的时候,就可以正常的访问fetchFramewindow.name,并调用父级的回调函数。

<script>
    var fetchFrame = window.parent.document.getElementById('fetch');
    if(fetchFrame) {
        const interval = setInterval(() => {
            const fetchFrameWindow = fetchFrame.contentWindow;
            const hostname = fetchFrameWindow.location;
            try {
                // 这时,请求已经完成
                if(fetchFrameWindow.location.hostname !== hostname) {
                    clearInterval(interval);
                    if(window.top && window.top.message) {
                        // 调用主页面声明的回调函数
                        window.top.message(fetchFrameWindow.name)
                    }
                }
            } catch (error) {
                // 捕获跨域异常
                console.log("还在请求当中")
            }
        }, 1000);
    }
</script>

效果如下

image.png

浏览器端实现utf8和gbk编码互转

在开发的过程中,有时难免会遇到编码问题。比如说服务端需要提交的参数是gbk,又或者我们需要使用到gbk的字符串。对于这些场景,使用JavaScript的encodeURI或者encodeURIComponent等浏览器的全局api都无法完成。但可以使用form标签的accept-charset,以下为文档上的描述

accept-charset 属性允许您指定一系列字符集,服务器必须支持这些字符集,从而得以正确解释表单中的数据。

举一个简单的例子

<form action="form_action.asp" accept-charset="gbk">
  <input  type="text" name="test" value="这里是一串字符串" />
  <input type="submit" value="Submit" />
</form>

尝试提交后,得到的结果是 image.png 我们可以看到请求的query上test后面跟着一串(unable to decode value), 这是因为gbk编码无法解析。

我们已经有了生成gbk编码的字符串的方法了。但是还是没法拿到。这个时候就可以用前面使用到的form标签的target。

image.png 使用target=“framename”就可以提交字符串到对应的iframe,而在iframe中我们就能拿到编码完成的字符串了。

<form target="encode" accept-charset="gbk" >
  <input  type="text" name="test" value="这里是一串字符串" />
  <input type="submit" value="Submit" />
</form>
<iframe name="encode" style="display:none" src='about:blank' id="encode"></iframe>
<script>
// 上面代码中创建了一个iframe标签和form标签,form标签会把表单提交到encode的iframe
var frame = document.getElementById("encode");

// 定义了一个回调函数
window.callback = function(result) {
    console.log(result);
}
// 从iframe的onload事件得到结果
frame.onload = function() {
     //打印frame的location.search可以得到 ?test=%D5%E2%C0%EF%CA%C7%D2%BB%B4%AE%D7%D6%B7%FB%B4%AE
    result = frame.contentWindow.location.search.split("=")[1]
    frame.contentWindow.parent.callback(result)
}
</script>

转码后得到以下结果

image.png