解决Safari等浏览器拦截window.open (自创版全网唯一)

1,305 阅读3分钟

在 Safari 浏览器中,window.open 有时会被拦截。这通常是因为Safari对弹出窗口有更严格的控制,尤其是在用户未明确触发的情况下。以下是一些常见原因和解决方法:

原因

  1. 用户未明确触发:如果 window.open 是在异步操作或回调函数中调用的,而不是直接由用户事件(如点击按钮)触发的,Safari 会拦截弹出窗口。
  2. 弹出窗口阻止设置:Safari 可能启用了阻止弹出窗口的设置。
  3. 跨域问题:在某些情况下,跨域操作可能导致弹出窗口被拦截。

解决方法

确保 window.open 是在用户明确触发的事件中调用,例如点击事件。以下是详细的代码示例和解释:

示例 1:直接在事件处理器中调用 window.open

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Window Open Example</title>
</head>
<body>
  <button id="openWindowButton">Open Window</button>

  <script>
    document.getElementById('openWindowButton').addEventListener('click', function() {
      window.open('https://www.example.com', '_blank');
    });
  </script>
</body>
</html>

在这个示例中,window.open 是在用户点击按钮时直接调用的,这样 Safari 通常不会拦截。

示例 2:在异步操作中调用 window.open

如果需要在异步操作中调用 window.open,可以通过在用户事件中预先创建一个窗口,然后在异步操作完成后修改窗口内容。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Window Open Example</title>
</head>
<body>
  <button id="openWindowButton">Open Window</button>

  <script>
    document.getElementById('openWindowButton').addEventListener('click', function() {
      // 预先创建一个空窗口
      const newWindow = window.open('', '_blank');

      // 模拟异步操作
      setTimeout(function() {
        // 异步操作完成后修改窗口内容
        newWindow.location.href = 'https://www.example.com';
      }, 1000);
    });
  </script>
</body>
</html>

在这个示例中,首先在用户点击时创建一个空窗口,然后在异步操作完成后修改窗口的内容。

示例 3:处理跨域问题

如果涉及跨域操作,确保目标URL是允许的,并且浏览器没有阻止该操作。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Window Open Example</title>
</head>
<body>
  <button id="openWindowButton">Open Window</button>

  <script>
    document.getElementById('openWindowButton').addEventListener('click', function() {
      // 预先创建一个空窗口
      const newWindow = window.open('', '_blank');

      // 检查新窗口是否成功创建
      if (newWindow) {
        // 模拟异步操作
        setTimeout(function() {
          // 异步操作完成后修改窗口内容
          newWindow.location.href = 'https://www.example.com';
        }, 1000);
      } else {
        alert('Popup blocked! Please allow popups for this website.');
      }
    });
  </script>
</body>
</html>

在这个示例中,添加了一个检查,如果新窗口未能成功创建,提示用户允许弹出窗口。

为了确保 window.open 在 Safari 中正常工作,关键是确保它在用户明确触发的事件(如点击)中直接调用。如果涉及异步操作,通过这些方法,可以有效避免 Safari 拦截 window.open

上面问题中, 预先创建一个空窗口,然后在异步操作完成后修改窗口内容,是网上一个通用的解决办法,但由于是先跳转通常需要提供成功和失败的界面,成本高,不完全通用。

大多数情况下是点击后由接口获取决定的url或者是否跳转,可以用如下封装解决

/**
 * 通用 异步 window.open 解决方案
 * @param {Object} options
 * options.callBack 包含异步的回调 必传
 * 使用示例 open()中是包含异步的逻辑
 link.handlePushAsync(async (cb) => {
    const url = await this.open();
    cb(url, {from: this.from})
 });
 */
async function handlePushAsync(callBack) {
    if (!callBack || typeof callBack !== 'function') return;

    let urlInit;
    let fromParamsInit = {};

    // 兼容性处理:如果浏览器不支持 requestAnimationFrame,则使用 setTimeout
    const requestAnimFrame = window.requestAnimationFrame || function (callback) {
        return setTimeout(callback, 16); // 16ms 大约是 60fps
    };

    const cancelAnimFrame = window.cancelAnimationFrame || clearTimeout;

    const step = () => {
        if (urlInit) {
            link.push(urlInit, fromParamsInit);
            cancelAnimFrame(frame);
            return;
        }
        requestAnimFrame(step);
    };

    const frame = requestAnimFrame(step);

    callBack((url, fromParams) => {
        urlInit = url;
        fromParamsInit = fromParams;
    });
}

注意该方法在接口返回时间过长还是会拦截,但胜在直接解决问题,正常情况下都是好用的