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

Axe*_*rks 20 javascript iphone mobile-safari dom-events ios

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

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

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

    Main text

  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

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

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

Dav*_*eck 15

我相信当应用程序进入后台时,计时器(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);
Run Code Online (Sandbox Code Playgroud)

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

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


rob*_*cat 7

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

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

  • window visibilitychange事件
  • 窗口焦点事件
  • 窗口pageshow事件
  • 启动计时器并查看计时器是否花费的时间比它应该的时间长(计时器在休眠时由iOS进入睡眠状态).即使在iOS9上,使用UIWebView的应用程序也不会触发visibilityChange事件(WKWebView没问题).

我曾经也使用过webkitRequestAnimationFrame,但是我删除了它,因为它可能导致jank(AFAIK渲染引擎对它的主线程执行阻塞调用).

要尝试的事情:

  • 转到另一个标签
  • 锁定屏幕,等待,解锁
  • 带来另一个应用程序专注
  • 最小化浏览器

您可以看到发生了哪些事件:

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

iOS:请注意,如果使用计时器来检测iOS UIWebView是否已进入睡眠状态,则需要使用new Date.getNow()和不使用来测量差异performance.now().这是因为performance.now()当页面进入睡眠状态时停止计算时间,iOS也很难实现性能.现在()...(旁白:你可以通过检测页面的差异来测量页面睡眠的时间长度.对于差异new Date.getNow()performance.now(),认准!=上测试页).

如果您正在使用UIWebView,那么有两种技术可行(如果您支持iOS7应用程序,则必须使用UIWebView).WKWebView具有visibilitychange事件,因此不需要变通方法.

==技术1.

当appWillEnterForeground事件发生在app中时,调用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();
});
Run Code Online (Sandbox Code Playgroud)


Sim*_*mon 6

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


ter*_*rse 5

我写了一个小测试页面来查看哪些事件被发送到 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;
  });
});
Run Code Online (Sandbox Code Playgroud)
#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;
  }
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)