简体   繁体   English

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

[英]How to detect in iOS webapp when switching back to Safari from background?

How can I build a webpage which is able to monitor when the page gets the focus, especially when Safari is in the background and the user switches Safari back to the foreground.如何构建一个能够监控页面何时获得焦点的网页,尤其是当 Safari 在后台并且用户将 Safari 切换回前台时。

The code below does not fire the event when switching to Safari on an iPhone在 iPhone 上切换到 Safari 时,以下代码不会触发该事件

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

    Main text

  </body>
</html>

According http://www.quirksmode.org/dom/events/index.html : Safari iPhone does not fire the event when the window gains the focus.根据http://www.quirksmode.org/dom/events/index.html :当窗口获得焦点时,Safari iPhone 不会触发该事件。

So my question is still: how to detect by using Javascript on a web page within Safari for iPhone that the window receives the focus?所以我的问题仍然是:如何通过在 Safari for iPhone 中的网页上使用 Javascript 来检测窗口接收焦点?

I believe timers (setInterval()) are suspended when the app enters the background.我相信计时器 (setInterval()) 在应用程序进入后台时会暂停。 You could do something like:你可以这样做:

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);

You may need to adjust those time intervals to suite your needs.您可能需要调整这些时间间隔以满足您的需要。

But, most likely, if it has been long enough to need a refresh (a few days) safari will probably reload the page because it is out of memory.但是,最有可能的是,如果它已经足够长需要刷新(几天),safari 可能会重新加载页面,因为它的内存不足。

Depending on what you need to support, you need a variety of different techniques to detect when a page becomes visible.根据您需要支持的内容,您需要多种不同的技术来检测页面何时可见。 Variations occur due to browser vendor, browser version, OS, running within WebView/UIWebView/WKWebView, etc.由于浏览器供应商、浏览器版本、操作系统、在 WebView/UIWebView/WKWebView 中运行等而发生变化。

You can view which events are occurring by using this page .您可以使用此页面查看正在发生的事件。 I have found that to detect when the page "wakes up" on all combinations I needed to register all of the following events:我发现要检测页面何时在所有组合上“唤醒”,我需要注册以下所有事件:

  • window visibilitychange event窗口可见性改变事件
  • window focus event窗口焦点事件
  • window pageshow event窗口页面显示事件
  • starting a timer and seeing if the timer took much longer than it should have (timers are put to sleep by iOS when hibernated).启动计时器并查看计时器是否比应有的时间长得多(计时器在休眠时被 iOS 置于睡眠状态)。 An App using UIWebView doesn't fire the visibilityChange event even on iOS9 (WKWebView is OK).使用 UIWebView 的应用程序即使在 iOS9 上也不会触发visibilityChange 事件(WKWebView 是可以的)。

I used to use webkitRequestAnimationFrame as well, but I removed that because it could cause jank (AFAIK the rendering engine does a blocking call to the main thread for it).我曾经也使用过 webkitRequestAnimationFrame,但我删除了它,因为它可能会导致卡顿(AFAIK 渲染引擎会为它阻塞调用主线程)。

Things to try:尝试的事情:

  • Go to another tab转到另一个选项卡
  • Lock screen, wait, unlock锁屏,等待,解锁
  • Bring another app to focus聚焦另一个应用
  • Minimise browser最小化浏览器

You can see which events are happening:您可以查看正在发生的事件:

  • in real time by looking at the console log (attach debugger).通过查看控制台日志(附加调试器)实时查看。
  • in real time on a device by using http://output.jsbin.com/rinece#http://localhost:80/ and see the log get requested as Ajax calls (use a proxy, or run a small server on the address after the # and log the body to console).通过使用http://output.jsbin.com/rinece#http://localhost:80/在设备上实时查看日志作为 Ajax 调用请求(使用代理,或在地址上运行小型服务器)在 # 之后并将正文记录到控制台)。
  • look at the on screen log, and pay close attention to the time logged for each entry to see if the entry was logged eg visibilitychange hide event might not occur when page is actually hidden (if app is hibernated), but instead is queued and occurs when page is reshown!!!查看屏幕日志,并密切注意为每个条目记录的时间,以查看该条目是否已被记录,例如,当页面实际隐藏时(如果应用程序处于休眠状态),可能不会发生visibilitychange hide 事件,而是排队并发生当页面重新显示时!!!

iOS: beware if using a timer to detect if an iOS UIWebView has gone to sleep, you need to measure the difference using new Date.getNow() and not performance.now() . iOS:请注意,如果使用计时器来检测 iOS UIWebView 是否已进入睡眠状态,则需要使用new Date.getNow()而不是performance.now()来测量差异。 That is because performance.now() stops counting time when the page is put to sleep also iOS was slow to implement performance.now()... (Aside: you may be able to measure the amount of time the page was asleep for by detecting the discrepency of differences for new Date.getNow() and performance.now() . Look for the != on the test page ).那是因为performance.now()在页面进入睡眠状态时停止计算时间,而且 iOS 实现 performance.now() 的速度很慢......(另外:您可以测量页面睡眠的时间通过检测new Date.getNow()performance.now()差异的差异。在测试页面上查找 != )。

If you are using UIWebView then there are two techniques that work (You must use UIWebViewif you support an iOS7 App).如果您使用的是 UIWebView,那么有两种技术有效(如果您支持 iOS7 应用程序,则必须使用 UIWebView)。 WKWebView has the visibilitychange event so workarounds are not required. WKWebView 具有可见性更改事件,因此不需要解决方法。

==Technique 1. ==技术1。

When the applicationWillEnterForeground event occurs in the app, call UIWebView stringByEvaluatingJavaScriptFromString to call your JavaScript pageAwakened().当应用程序中发生 applicationWillEnterForeground 事件时,调用 UIWebView stringByEvaluatingJavaScriptFromString 来调用您的 JavaScript pageAwakened()。

Benefits: clean, accurate.优点:干净、准确。

Downside: needs Objective-C code.缺点:需要 Objective-C 代码。 Called function needs to be accessable from global scope.被调用的函数需要可从全局范围访问。

==Technique 2. ==技术2。

Use webkitRequestAnimationFrame and detect a time lag.使用 webkitRequestAnimationFrame 并检测时间延迟。

Benefits: JavaScript only.优点:仅限 JavaScript。 Works for mobile Safari on iOS7.适用于 iOS7 上的移动 Safari。

Downside: ugly risk of jank and using webkitRequestAnimationFrame is a severe hack.缺点:卡顿的丑陋风险和使用 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();
});

The Page Visibility API would probably offer a solution to this Problem. Page Visibility API可能会为这个问题提供解决方案。 I guess this API has not been implemented in Mobile Safari yet, at least I haven't found any documentation for an iOS implementation.我猜这个 API 还没有在 Mobile Safari 中实现,至少我还没有找到任何关于 iOS 实现的文档。 However, an Implementation has been commited to the Webkit Trunk, so there is a chance that it will be supported by future Versions of Mobile Safari.但是,Webkit Trunk 已经提交了一个实现,因此它有可能被未来版本的 Mobile Safari 支持。

I wrote a little test page to see what events are being sent to the window on iOS.我写了一个小测试页面来查看哪些事件被发送到 iOS 上的窗口。

The page is "Apple web app capable", so you can save it to the home screen and test it in standalone mode.该页面是“支持 Apple 网络应用程序”,因此您可以将其保存到主屏幕并在独立模式下进行测试。

Here's the page: Test of Window Events这是页面:窗口事件的测试

The code:代码:

 // 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>

The focus and blur events on the window are good to detect if the browser is going to or back from background.窗口上的 focus 和 blur 事件可以很好地检测浏览器是从后台进入还是从后台返回。 This worked for me on iOS8 Safari:这在 iOS8 Safari 上对我有用:

window.addEventListener("focus", function(evt){
    console.log('show');
}, false);
window.addEventListener("blur", function(evt){
    console.log('hide');
}, false);

由于问题出在 mobile safari 并且它支持 popstate 事件,因此您可以使用此事件来检测用户何时回来

Use the pageshow and pagehide events.使用pageshowpagehide事件。

<script type="text/javascript">
        window.addEventListener("pageshow", function(evt){
            alert('show');
        }, false);
        window.addEventListener("pagehide", function(evt){
            alert('hide');
        }, false);
</script>

https://developer.apple.com/library/mac/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html https://developer.apple.com/library/mac/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM