从后台切换回Safari时如何在iOS webapp中检测?

IT技术 javascript ios iphone mobile-safari dom-events
2021-02-19 20:56:33

如何构建一个能够监控页面何时获得焦点的网页,尤其是当 Safari 在后台并且用户将 Safari 切换回前台时。

在 iPhone 上切换到 Safari 时,以下代码不会触发该事件

<html>
  <head>
    <script type="text/javascript">
      window.onfocus = function() { alert("onfocus"); };
    </script>
  </head>
  <body>

    Main text

  </body>
</html>

根据http://www.quirksmode.org/dom/events/index.html:当窗口获得焦点时,Safari iPhone 不会触发事件。

所以我的问题仍然是:如何通过在 Safari for iPhone 中的网页上使用 Javascript 来检测窗口接收焦点?

6个回答

我相信计时器 (setInterval()) 在应用程序进入后台时会暂停。你可以这样做:

var lastFired = new Date().getTime();
setInterval(function() {
    now = new Date().getTime();
    if(now - lastFired > 5000) {//if it's been more than 5 seconds
        alert("onfocus");
    }
    lastFired = now;
}, 500);

您可能需要调整这些时间间隔以满足您的需要。

但是,最有可能的是,如果它已经足够长需要刷新(几天),safari 可能会重新加载页面,因为它的内存不足。

例如,如果您让 webapp 全屏显示并按下“锁定”按钮,然后解锁屏幕,则此方法有效。但是,如果您通过按主页按钮切换应用程序,则它不起作用,因为这会导致整个 Web 应用程序从头开始重新加载。我想在这种情况下这无关紧要,因为无论如何你都会有东西在加载。但正如@EricFalsken 提到的,您无法收到任何卸载通知。
2021-04-22 20:56:33
如果另一个前台应用程序需要更多资源,safari 中的网页是首先被卸载的东西。如果另一个应用程序需要更多内存并且 Safari 选择卸载您的网页,您将不会收到任何卸载通知,并且在您返回时网页将重新加载。
2021-05-18 20:56:33
如果我们在前台没有任何活动的情况下离开 safari,它甚至会刷新。因此,如果有人正在阅读新闻,页面会重新加载。我们想要的是它应该只在我们从后台返回到前台时重新加载。
2021-05-19 20:56:33
但是这种方法有问题。当用户滚动页面时, setInterval 也会暂停。因此,如果用户滚动页面超过 5 秒,警报也会被触发 - 这可能是不希望的。
2021-05-20 20:56:33

根据您需要支持的内容,您需要多种不同的技术来检测页面何时可见。由于浏览器供应商、浏览器版本、操作系统、在 WebView/UIWebView/WKWebView 中运行等而发生变化。

您可以使用此页面查看正在发生的事件我发现要检测页面何时在所有组合上“唤醒”,我需要注册以下所有事件:

  • 窗口可见性改变事件
  • 窗口焦点事件
  • 窗口页面显示事件
  • 启动计时器并查看计时器是否比应有的时间长得多(计时器在休眠时被 iOS 置于睡眠状态)。使用 UIWebView 的应用程序即使在 iOS9 上也不会触发visibilityChange 事件(WKWebView 是可以的)。

我曾经也使用过 webkitRequestAnimationFrame,但我删除了它,因为它可能会导致卡顿(AFAIK 渲染引擎会为它阻塞调用主线程)。

尝试的事情:

  • 转到另一个选项卡
  • 锁屏,等待,解锁
  • 聚焦另一个应用
  • 最小化浏览器

您可以查看正在发生的事件:

  • 通过查看控制台日志(附加调试器)实时查看。
  • 通过使用http://output.jsbin.com/rinece#http://localhost:80/在设备上实时查看日志作为 Ajax 调用请求(使用代理,或在地址上运行小型服务器)在 # 之后并将正文记录到控制台)。
  • 查看屏幕上的日志,并密切注意为每个条目记录的时间,以查看该条目是否已被记录,例如,当页面实际隐藏时(如果应用程序处于休眠状态),visibilitychange hide 事件可能不会发生,而是排队并发生当页面重新显示时!!!

iOS:请注意,如果使用计时器来检测 iOS UIWebView 是否已进入睡眠状态,则需要使用new Date.getNow()而不是来测量差异performance.now()那是因为performance.now()当页面进入睡眠状态时停止计算时间,而且 iOS 实现 performance.now() 的速度也很慢......(另外:您可以通过检测对于差异new Date.getNow()performance.now(),认准!=上测试页)。

如果您使用的是 UIWebView,那么有两种技术有效(如果您支持 iOS7 应用程序,则必须使用 UIWebView)。WKWebView 具有可见性更改事件,因此不需要解决方法。

==技术1。

当应用程序中发生 applicationWillEnterForeground 事件时,调用 UIWebView stringByEvaluatingJavaScriptFromString 来调用您的 JavaScript pageAwakened()。

优点:干净、准确。

缺点:需要 Objective-C 代码。被调用的函数需要可从全局范围访问。

==技术2。

使用 webkitRequestAnimationFrame 并检测时间延迟。

优点:仅限 JavaScript。适用于 iOS7 上的移动 Safari。

缺点:卡顿的丑陋风险和使用 webkitRequestAnimationFrame 是一个严重的黑客。

// iOS specific workaround to detect if Mobile App comes back to focus. UIWebView and old iOS don't fire any of: window.onvisibilitychange, window.onfocus, window.onpageshow
function iosWakeDetect() {
    function requestAnimationFrameCallback() {
        webkitRequestAnimationFrame(function() {
            // Can't use timestamp from webkitRequestAnimationFrame callback, because timestamp is not incremented while app is in background. Instead use UTC time. Also can't use performance.now() for same reason.
            var thisTime = (new Date).getTime();
            if (lastTime && (thisTime - lastTime) > 60000) {    // one minute
                // Very important not to hold up browser within webkitRequestAnimationFrame() or reference any DOM - zero timeout so shoved into event queue
                setTimeout(pageAwakened, 0);
            }
            lastTime = thisTime;
            requestAnimationFrameCallback();
        });
    }
    var lastTime;
    if (/^iPhone|^iPad|^iPod/.test(navigator.platform) && !window.indexedDB && window.webkitRequestAnimationFrame) {    // indexedDB sniff: it is missing in UIWebView
        requestAnimationFrameCallback();
    }
}

function pageAwakened() {
    // add code here to remove duplicate events. Check !document.hidden if supported
};

window.addEventListener('focus', pageAwakened);
window.addEventListener('pageshow', pageAwakened);
window.addEventListener('visibilitychange', function() {
    !document.hidden && pageAwakened();
});

网页浏览权限API可能会提供一个解决这个问题。我猜这个 API 还没有在 Mobile Safari 中实现,至少我还没有找到任何关于 iOS 实现的文档。然而,Webkit Trunk 已经提交了一个实现,因此它有可能被未来版本的 Mobile Safari 支持。

你找到解决这个问题的方法了吗?似乎 PageVisibility API 仍然无法与主页按钮一起使用...
2021-04-26 20:56:33
这是一个不错的主意,但它现在已在 iOS7 ( caniuse.com/#feat=pagevisibility ) 中实现,但它本身并不能解决问题。如果您在 Safari 中切换选项卡,它会起作用,但是如果您切换应用程序(通过单击或双击主页按钮),则不会。
2021-04-30 20:56:33
页面可见性 API 在 safari 和 chrome 中对我有用。查看developer.mozilla.org/en-US/docs/Web/Guide/User_experience/...
2021-05-02 20:56:33
这目前正在移动 Safari iOS 14.2 上工作。当我点击主页按钮使应用程序后台运行时,我会收到事件,然后当我点击应用程序图标进行恢复时,以及在应用程序之间切换时。
2021-05-14 20:56:33
对于 iOS 上的应用程序,我的测试表明,无论您使用的是哪个 iOS 版本,UIWebView 中都不会发生visibilityChange 事件。它确实发生在 WKWebView 中。
2021-05-16 20:56:33

我写了一个小测试页面来查看哪些事件被发送到 iOS 上的窗口。

该页面具有“Apple Web 应用程序功能”,因此您可以将其保存到主屏幕并在独立模式下进行测试。

这是页面:窗口事件的测试

代码:

// determine if this is a touch-capable device
const isTouchDevice = ('ontouchstart' in window) ||
  (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);
console.log(`isTouchDevice: ${isTouchDevice ? 'TRUE' : 'FALSE'} `);

const button = document.getElementById('btnClear');
const divEvents = document.getElementById('divEvents');
const olEvents = document.getElementById('olEvents');
const divBottom = document.getElementById('divBottom');

// handle "clear history" button click
button.addEventListener('click', function() {
  if (isTouchDevice) {
    // simulate click on button using `focus` and `blur`
    button.focus();
    setTimeout(() => button.blur(), 500);
  }
  olEvents.innerHTML = '';
});

const eventNames = [
  'load',
  'focus',
  'blur',
  'change',
  'close',
  'error',
  'haschange',
  'message',
  'offline',
  'online',
  'pagehide',
  'pageshow',
  'popstate',
  'resize',
  'submit',
  'unload',
  'beforeunload'
];
eventNames.forEach(function(eventName) {
  window.addEventListener(eventName, function(evt) {
    const now = new Date();
    const timeStr = now.getHours().toString().padStart(2, '0') + ':' +
      now.getMinutes().toString().padStart(2, '0') + ':' +
      now.getSeconds().toString().padStart(2, '0') + '.' +
      now.getMilliseconds();
    let li = document.createElement('li');
    li.innerHTML = timeStr + ' - ' + `<code>${evt.type}</code>`;
    olEvents.appendChild(li);

    // scroll to bottom
    // window.scrollTo(0, divBottom.offsetTop);
    const bottomOffset = divBottom.offsetTop;
    divEvents.scrollTop = bottomOffset - 10;
  });
});
#divEvents {
  border: 1px solid rgba(0, 0, 0, 0.5);
  height: 400px;
  max-width: 60rem;
  padding: 1rem 0;
  overflow-y: auto;
}

#olEvents {
  font-size: 87.5%;
}

#divBottom {
  height: 0px;
}

code {
  font-size: 100%;
}


/* handle the sticky hover problem on touch devices */

@media (hover:none) {
  /* set button hover style to match non-hover */
  .btn-outline-primary:hover {
    color: #007bff;
    background-color: transparent;
    background-image: none;
    border-color: #007bff;
  }
  /* set button focus style to match hover */
  .btn-outline-primary:focus {
    color: #fff;
    background-color: #007bff;
    border-color: #007bff;
  }
}
<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, maximum-scale=1, minimum-scale=1, shrink-to-fit=no, user-scalable=no">

  <!-- apple web app meta tags -->
  <meta name="apple-mobile-web-app-title" content="WinEvents">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">

  <title>Test of Window Events</title>

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootsdark@latest/dist/bootsdark.min.css">

</head>

<body class="d-flex flex-column h-100">
  <header>
    <!-- Fixed navbar -->
    <nav class="navbar navbar-expand-md navbar-dark bg-dark">
      <a class="navbar-brand" href="https://terrymorse.com">Terry Morse
      Software</a>
    </nav>
  </header>

  <main role="main" class="flex-shrink-0 m-4">
    <h1>Test of Window Events</h1>

    <p>Displays all of the events (except for
      <code>scroll</code>) sent to <code>document.window</code>.</p>

    <p>
      <button id="btnClear" class="btn btn-sm btn-outline-primary"
      >Clear History</button>
    </p>

    <h4>Events Caught:</h4>
    <div id="divEvents">
      <ol id="olEvents" class="text-monospace"></ol>
      <div id="divBottom"></div>
    </div>
  </main>

</body>

在 i(Pad)OS 15.1 上,当浏览器返回前台时,似乎有一个“调整大小”事件(实际上是其中 3 个)。iPadOS 15.2 或 16 会是什么,谁也猜不到……
2021-05-20 20:56:33

窗口上的 focus 和 blur 事件可以很好地检测浏览器是要进入后台还是从后台返回。这在 iOS8 Safari 上对我有用:

window.addEventListener("focus", function(evt){
    console.log('show');
}, false);
window.addEventListener("blur", function(evt){
    console.log('hide');
}, false);
我在 iPhone XS 和 iPad Pro、iOS 12.1 上测试了“焦点”事件检测。它正在工作,尽管每次激活 Safari 时都会检测到两个焦点事件。
2021-04-23 20:56:33