🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
一、前言
今天在开发H5的时候,遇到了一个bug,就是在ios环境,在某些情况下执行window.open
不生效,所以正好趁此机会研究了一下window.open
。
二、window.open介绍
从open
方法的调用方式可以看出,open
方法是定义在Window
接口上,正因为如此它有三个参数:
window.open(url, target, windowFeatures)
- url:「可选参数」,表示你要加载的资源URL或路径,如果不传,则打开一个
url
地址为about:blank
的空白页。
顺便介绍一下,about:blank
是chrome浏览器
的一个命令,该指令会打开浏览器的一个内建空白页面,而不是从网络上下载,类似的命令还有about:downloads
、about:extensions
、about:history
等等,具体chrom浏览器
提供了哪些命令,你可以通过在浏览器地址栏输入about:about
查看。
- target:「可选参数」,它可以给以下两种值。
- 第一种是target关键字
_self
:当前标签页加载;_blank(默认值)
:新标签页打开;_parent
:作为当前浏览环境的父级浏览上下文打开,没有父级浏览上下文,效果与_self
相同;_top
:作为最顶级的浏览上下文打开,没有顶级浏览上下文,效果与_self
相同。
- 第二种是一个字符串:表示加载资源的浏览上下文的名称,也就是标签页的名称,如果这个名称在现有的标签页中不存在,则会开启一个新的标签页,如果存在,会跳转到这个标签页。
- 第一种是target关键字
这里顺便提一下,我在平时开发中曾经写出这样的代码:
const handleClick = () => {window.open(url, 'blank'); }
这个方法是绑定在一个点击按钮上面的,结果我的同事在点击多次这个按钮时,第一次会打开一个新窗口,而后续的点击都跳转到第一次点击打开的那个窗口,于是我去排查了一下,发现自己把_blank
写成了blank
,于是浏览器把它解析成了标签页的名字,而不是target关键词,我那时候感觉挺有意思,第一次详细去了解了这个target
。
- windowFeatures:「可选参数」,它是一个字符串,用来描述窗口的特性,其格式是
"key1=value1, key2=value2"
,即将key
和value
以=
号连接拼接成字符串,多个key value
以逗号隔开,比如我们要打开一个宽为500,高为600的窗口可以这么写:
window.open(url, 'new-window', 'width=500,height=600');
它可以描述如下窗口特性:
width
:内容区域宽度,最小值为100,height
:内容区域高度,最小值100,left
:距离用户操作系统工作区左侧的距离,top
:距离用户操作系统工作区顶部的距离。- ...
这四个应该是最常用的,其它的不太常用我这里就不列举了。
至于这个windowFeatures
的作用呢,平常开发中用的不太多,我能想到的场景就是比如你要通过url打开一个预览页面,让用户看里面的一些内容,就可以用这个试试。
三、bug复现
先写一个能复现问题的demo:
async function jump() {await fetch('/xxx');window.open('https://www.xxx.cn'); }
正常情况下执行window.open
是能正常新标签页打开传入的url的,但是一旦前面用await
做了异步操作后,再执行window.open
,就不生效了。
然后我又尝试了a标签
,发现效果也是一样的,无法打开新标签页。
async function jump() {await fetch('/xxx');let a = document.createElement('a');a.setAttribute('target', 'blank');a.href = 'https://www.xxx.cn';a.click()a = null; }
四、原因分析
- 安全机制拦截:IOS的Safari浏览器为了防止恶意网站通过
window.open/a标签
打开其他网站,于是对它们的调用有所限制,如果不是由用户直接交互触发的,而是由程序自动触发的,Safari会拦截这个操作。 - 异步操作:在AJAX回调中执行
window.open/a标签跳转
,被浏览器认为是非用户交互行为,所以被拦截。
五、解决方案
方案1:改用location.href
既然window.open
和a标签跳转
不行,那就换成location.href
就好了,因为safari不会拦截location.href
。
async function jump() {await fetch('/xxx');location.href = 'https://www.xxx.cn'; }
当然并不是所有场景下都适合用location.href
,因为location.href
会刷新页面,所以需要根据具体场景来选择。
方案2:先打开一个空标签页
通过window.open("", "_blank")
先打开一个空标签页,然后等待请求完成后,修改这个新标签页的url。
async function jump() {const newWin = window.open("", "_blank"); // 提前打开一个窗口const { jumpUrl } = await fetch('/xxx');if (jumpUrl) {newWin.location = jumpUrl;} else {newWin.close();// ... } }
但这里有个体验问题,我这里根据有没有jumpUrl
进行跳转,如果没有jumpUrl
,我需要调用close
方法关闭刚才提前打开的那个窗口,而这样用户就会体验到的流程就是,先出来一个新窗口,随后被秒关闭,这样用户体验很差。
方案3:setTimeout/requestAnimationFrame
在我的业务场景中,是必须要用window.open的,所以只能另寻他法,最终找到了一个解决方案,就是在window.open
之前加一个setTimeout
,在回调中执行window.open
,这样就能避免被safari拦截。
async function jump() {await fetch('/xxx');setTimeout(() => {window.open('https://www.xxx.cn');}, 0) }
后面测试了一下,发现requestAnimationFrame
也可以。
async function jump() {await fetch('/xxx');requestAnimationFrame(() => {window.open('https://www.xxx.cn');}) }
六、最终我采取的方案
我最终是通过方案3的setTimeout
解决了问题,如果setTimeout
不生效,可以尝试加点延时看看,比如100毫秒,我这边实测的ios机型都能生效,所以就没加延时。
七、小结
本文主要介绍了window.open
的用法,以及我自己在平时开发中踩的坑,希望对大家平常开发有帮助!