异步请求后,再window.open打开新窗口被浏览器拦截方案

7,092 阅读2分钟

众所周知,在平时button或a标签点击事件触发window.open打开新窗口是没有问题的。而如果在点击事件中先进行一些异步请求再去window.open则会被一些浏览器拦截,如下图所示,原因是命中了浏览器的安全策略,在浏览器看来打开窗口并非用户直接操作导致,有可能是一个广告等并非用户想要看到的内容。那么,是否有一些方案可以避免拦截呢?以下为笔者在解决问题过程中试过的几个方案。

safari浏览器拦截

方案一 模拟a标签点击事件(还是会被一些浏览器拦截)

代码如下:

// 模拟a标签点击事件
function newWin(url) {
  const a = document.createElement('a');
  a.href = url;
  const event = new MouseEvent('click');
  a.dispatchEvent(event);
}

// 异步请求后,触发a标签点击事件
axios.post('xxxx', {
  a: '1'
}).then((res) => {
  const { url } = res;
  newWin(url);
})

该方案并没能解决浏览器拦截问题,根本原因还是点击事件非用户所为。

方案二 先window.open打开空白窗口,待异步返回后再填充url

代码如下:

// 先打开新的空白窗口
const winRef = window.open(url, '_blank');
// xxx 为新打开窗口的title,不设置的话新窗口title会出现暂时的”无标题“
winRef.document.title = 'xxx';

axios.post('xxx', {
  a: 'a'
}).then((res) => {
  const { url } = res;
  // 将新窗口地址置为url
  winRef.location.href = url;
})

该方案亲测是有效的,可以解决浏览器拦截问题,但是在异步请求期间,新打开的窗口会出现空白页,对用户体验不是太友好。 空白窗口

那么,是否有其他更好的方案呢?

在查阅资料时,发现了一些浏览器是根据用户点击事件之后1秒内是否触发window.open来触发拦截的。那么基于此可以通过设置setTimeout 1s时间再触发window.open减少新空白窗口白屏时间,即方案三。

方案三 setTimeout和window.open打开空白页一起使用

let winRef;
let targetUrl;

// 延迟1000ms
setTimeout(() => {
  winRef = window.open(targetUrl, '_blank');
  winRef.document.title = 'xxx';
}, 1000);

axios.post('xxx', {
  a: 'a'
}).then((res) => {
  const { url } = res;
  // 判断winRef是否有值,如有则已打开了新窗口,则将新窗口地址置为url
  // 否则将url赋给targetUrl即可
  if(winRef) {
  	winRef.location.href = url;
  } else {
  	targetUrl = url;
  }
})

代码中延迟1000ms为最大延迟时间,如果大于此时间还是会被拦截。但是如果异步接口实际时间小于1000ms,那么可以根据实际情况合理的设置。 此方案在chrome、火狐、safari、360等浏览器亲测有效。

方案四 接入层302跳转

第四种是笔者解决异步跳转比较推崇的方案,即在新开tab中填充url触发get请求,在接入层中完成异步接口请求后,在接入层直接302到目标url。