You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Safari未安装自定义协议却触发onblur事件的解决方案咨询

解决Safari中自定义协议检测的onblur误判问题

这个问题我之前做自定义协议检测的时候也踩过坑,Safari弹出的那个「无法打开页面」系统提示框,会直接夺走页面焦点触发onblur,导致我们误判成协议已成功打开(毕竟正常打开外部应用也会触发onblur)。下面是几个我亲测有效的解决方案:

方案1:优化焦点判断逻辑(兼容多浏览器)

核心思路是:onblur触发后别急着下定论,延迟一小段时间看看页面会不会重新获得焦点。如果是Safari的弹窗导致的失焦,弹窗关闭后焦点会自动回到页面;如果真的打开了外部应用,页面会一直处于失焦状态。

修改后的代码示例(结合jQuery):

function checkProtocol() {
    // 创建隐藏的输入框用来聚焦(避免影响页面布局)
    $('<input>', {
        id: 'focusInput',
        type: 'text',
        css: {
            background: 'transparent',
            border: 'none',
            height: 0,
            width: 0,
            position: 'absolute',
            top: '-100px', // 彻底移出可视区域
            outline: 'none'
        }
    }).appendTo('body');

    const focusEl = $('#focusInput')[0];
    let isProtocolValid = false;
    let blurTimer = null;

    // 监听页面重新获得焦点
    $(window).on('focus', function() {
        if (blurTimer) {
            clearTimeout(blurTimer);
            // 焦点回来了,说明是弹窗搞的鬼,协议没安装
            updateResult(false);
            cleanup();
        }
    });

    focusEl.focus();
    focusEl.onblur = function() {
        // 延迟800ms判断,给Safari弹窗留足关闭时间
        blurTimer = setTimeout(() => {
            // 过了延迟还没回焦点,说明真的打开了外部应用
            isProtocolValid = true;
            updateResult(true);
            cleanup();
        }, 800);
    };

    // 触发协议跳转
    location.href = protocolStr;

    // 兜底超时逻辑,防止极端情况
    setTimeout(() => {
        if (!isProtocolValid) {
            updateResult(false);
            cleanup();
        }
    }, 1500);

    // 清理临时元素和事件监听
    function cleanup() {
        focusEl.onblur = null;
        $(window).off('focus');
        $('#focusInput').remove();
        clearTimeout(blurTimer);
    }
}

方案2:Safari专属优化——用window.open监听窗口状态

Safari对window.open的处理有个特性:如果协议未安装,调用window.open(protocolStr)要么返回null,要么打开的窗口会立刻关闭;如果协议已安装,外部应用启动后,这个临时窗口会很快进入关闭状态。我们可以通过监听窗口的关闭事件来判断:

function checkSafariProtocol() {
    // 先判断是不是Safari(排除Chrome伪装的Safari UA)
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    if (!isSafari) {
        checkChrome(); // 非Safari用你原来的逻辑
        return;
    }

    const protocolWindow = window.open(protocolStr);
    let checkTimer = null;

    checkTimer = setInterval(() => {
        if (!protocolWindow || protocolWindow.closed) {
            clearInterval(checkTimer);
            // 窗口直接关闭或打不开,说明协议没安装
            updateResult(false);
        } else {
            try {
                // 尝试关闭窗口,如果能关掉,说明是无效协议的弹窗
                protocolWindow.close();
                clearInterval(checkTimer);
                updateResult(false);
            } catch (e) {
                // 关不掉,说明外部应用已经启动了,协议有效
                clearInterval(checkTimer);
                updateResult(true);
            }
        }
    }, 300);

    // 兜底超时,防止定时器卡死
    setTimeout(() => {
        clearInterval(checkTimer);
        if (protocolWindow && !protocolWindow.closed) {
            try {
                protocolWindow.close();
            } catch (e) {}
        }
        updateResult(false);
    }, 2000);
}

方案3:补充方案——利用iframe的错误事件(可靠性稍弱)

这个方法在部分Safari版本中可能因为安全限制不触发error事件,但可以作为兜底尝试:

function checkProtocolWithIframe() {
    const iframe = $('<iframe>', {
        src: protocolStr,
        css: {display: 'none'}
    }).appendTo('body');

    iframe.on('error', function() {
        // 加载出错,大概率是协议未安装
        updateResult(false);
        iframe.remove();
    });

    setTimeout(() => {
        // 超时没报错,假设协议有效
        updateResult(true);
        iframe.remove();
    }, 1000);
}

注意:这个方法在iOS Safari上的兼容性不如桌面端,谨慎使用。

总结

  • 优先选方案1,兼容多浏览器,能完美避开Safari弹窗的干扰;
  • 如果只需要针对Safari做优化,方案2更直接,利用Safari的特性判断更准确;
  • 方案3可以作为兜底,但可靠性不如前两个。

内容的提问来源于stack exchange,提问作者alasdair009

火山引擎 最新活动