window.open() 的作用是创建一个新的浏览器窗口用来打开相关的资源,这是一个原生的 JavaScript API。有关 window.open() 的基本使用可以参考 Mozilla 提供的 API 文档:window.open。
大部分现代的浏览器(泛指 Chrome / Firefox / IE 10+ / Safari)都默认开启了阻止弹出窗口的策略,原因是 window.open 被广告商滥用,严重影响用户体验。这个阻止弹出窗口的操作,并不是直接封杀 window.open(),而是会根据用户的行为来判断这次 window.open() 是否属于流氓操作。
如果是由用户触发的动作所引起的 window.open 就不会被浏览器所阻止,比如写在 onclick 这些事件 handler 里的;但如果是代码自己触发的就会被阻止。
大家可以打开以下两个页面进行测试:
- 点击按钮直接弹出窗口(demo1)
- 代码自动在两秒后弹出窗口(demo2)
如果你的浏览器是默认设置(开启了阻止弹出窗口),那么你就能发现,demo1 中点击完按钮后窗口就直接弹出来了,而 demo2 则在两秒后会被浏览器告知有一个窗口被阻止了。
但是问题来了:如果是用户点击后,我们的程序需要运行一些代码然后才执行 window.open(),这种情况是否会被阻止呢?
setTimeout 延迟的临界值
我们来做个简单的例子:点击按钮之后将 window.open() 延迟一下,看看浏览器们有什么反应。我们可以使用 setTimeout() 来做这个处理:
$('#test-btn').on('click', function(){
setTimeout(function(){
var windowOpen = window.open('http://lingyi.red', 'lingyired', 'status=no,menubar=no,titlebar=no,toolbar=no,directories=no, width=800,height=600, top=0, left=0');
if (windowOpen == null || typeof(windowOpen)=='undefined'){
$('#feedback').html('窗口无法打开,请检查你的浏览器设置。')
} else {
$('#feedback').html('窗口打开成功了.' );
}
}, 100);
});
当延迟 100 毫秒的时候,Chrome 会让窗口弹出;但是当延迟 2000 毫秒(即 2s)时这个操作会被阻止。经过多次试验,我发现这个临界值是 1000 毫秒。
1000 毫秒的时候允许弹出框,1001 毫秒的时候被阻止。
大家可以通过 demo3(点击这里)来测试,修改代码中的延迟值来验证。这个临界值在 Safari / Chrome / Firefox(都是 for Mac)下测试均通过,都是 1001 毫秒的时候被阻止。
结果拿到 IE 11 下测试,发现就算是 1 毫秒都会被阻止(意料之中)。
同步逻辑运算的容忍度
我们先撇开 IE 的差异,尝试把 setTimeout 转换成一些逻辑代码放在 window.open() 前面:
$('#test-btn').on('click', function(){
var nowtime = new Date().getTime();
for (var i = 0; i <= 300000000; i++) {
if (i == 111111) {
console.log(i);
};
};
console.log(new Date().getTime() - nowtime);
windowOpen = window.open('http://lingyi.red/', 'lingyired', 'status=no,menubar=no,titlebar=no,toolbar=no,directories=no, width=800,height=600, top=0, left=0');
if (windowOpen == null || typeof(windowOpen)=='undefined'){
$('#feedback').html('已触发:窗口无法打开,请检查你的浏览器设置。')
} else {
$('#feedback').html('已触发:窗口打开成功了')
}
console.log(new Date().getTime() - nowtime);
});
通过修改循环次数来不断实验,发现临界值最终大概在 950-1050 之间的区域就会触发阻止弹出窗,而大于这个值的,基本都会直接阻止弹出窗。之所以不是精确的 1000,应该是在运行过程中有轻微影响到了时间值。
整体来说,Safari / Chrome / Firefox(桌面版)对于用户点击事件后的 window.open() 有 1s 的延迟容忍度。
返回来测试下 IE 11 的表现,发现:
- setTimeout 内的 window.open() 一律都被阻止,哪怕只是延迟 1 毫秒
- 对于逻辑运算的延迟则有比较大的容忍度,至少 3s 内依然被放行(没有再继续测试下去)
然后顺便测试了 Safari for iPad 8.0 的情况:
- 根据用户的设置(设置 → Safari → 阻止弹出窗口),无差别阻止或允许窗口的弹出
- 如果用户去掉了阻止弹出窗口的选项,那么无论是由用户点击的还是程序自行触发的,统统会弹出一个窗口询问用户是否要打开新的窗口
小结
如果是由用户点击触发的 window.open() 代码前面,可以加上少量简单的逻辑代码来运行,但请控制在执行时间为 1s 内。
不过由于各种原因,我们的 window.open() 会经常性地被阻止掉。虽说浏览器本身有对这个阻止事件做通知,但由于用户一般注意不到,最好在界面上也做一些通知告知用户。
利用返回值判断是否被阻止
window.open() 的返回值是一个打开的新窗口对象的引用;如果 window.open() 的操作被阻止,那么它的返回值就是 undefined。
那么我们就可以利用返回值来判断这个 window.open() 操作是否被正常执行:
var windowOpen = window.open('http://lingyi.red/', 'lingyired', 'status=no,menubar=no,titlebar=no,toolbar=no,directories=no, width=800,height=600, top=0, left=0');
if (windowOpen == null || typeof(windowOpen)=='undefined'){
$('#feedback').html('已触发:窗口无法打开,请检查你的浏览器设置。')
} else {
$('#feedback').html('已触发:窗口打开成功了')
}
这样的话,就可以在界面上比较清晰地告知用户是怎么回事了。
扩展阅读
大家知道,如果父子窗口(当前窗口为父,window.open() 后的窗口为子)符合同源策略的话,父窗口里面是可以控制子窗口的,包括刷新、执行函数、跳转链接。
这里有一个方法可以绕开浏览器阻止弹窗的机制(针对桌面):点击按钮后先打开一个符合同源策略的 window,然后再执行你相关的逻辑包括 Ajax 请求等。完成之后,再来处理子窗口。假设 windowRef 是 window.open() 的返回值:
- windowRef.close() 是关闭窗口
- windowRef.location.href = url 是跳转链接
但是这种方法的体验不是很好:会预先弹出一个窗口。如果你运行的逻辑代码很长的话,用户会不知道要干什么,可能会直接关掉窗口。当然你们也可以在子窗口那里先留下一些文字,告诉用户不要关闭、请等待之类的。