其实这个标题略微有点标题党:iOS 中,除了少数服务(如播放音乐),大部分 App 在用户按了 Home 键以后,过不了多久就会被彻底冻结,这对 Safari 一样适用。本文不考虑这样状况,只考虑 Safari 运行时,怎样让定时器持续工做。html
咱们知道:PC 上的 Firefox、Chrome 和 Safari 等浏览器,都会自动把未激活页面中的 JavaScript 定时器(setTimeout、setInterval)间隔最小值改成 1 秒以上。这是由于间隔很小的定时器通常用来作 UI 更新(例如用定时器实现的动画),让用户不可见的页面上的定时器跑慢一些,既节省资源又不会影响体验。对移动浏览器来讲,内存、CPU、带宽等资源更加宝贵,移动设备上的浏览器每每会直接冻结全部未激活页面上的全部定时器。ios
我写了一个简单的 Demo 来讲明这个问题:web
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta content="width=320,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" />
</head>
<body>
<div id="test" style="font-size:32px;"></div>
<script>
var count = 0,
test = document.getElementById('test');
setInterval(function() {
count++;
test.innerHTML = count;
document.title = count;
}, 1 * 1000);
</script>
</body>
</html>
复制代码
在装有 iOS7 的 iPad 下测试,能够很清楚看到定时器被冻结的现象:跨域
可是,这种策略有时也不那么完美。对 Web 邮箱、SNS 等网站来讲,让用户及时知道有新消息很重要。不少网站会定时获取消息数,有新信息时在页面标题加上消息数,这样即便用户在浏览其它 Tab 时也能够看到提醒,这种弱提醒会给用户带来良好的产品体验。iOS 上,这种作法因为定时器被冻结而变得行不通了。浏览器
既然 iOS 的浏览器(我测试了 Safari 和 Chrome)都会冻结非激活页面的 JS 定时器,那么必须另辟蹊径了。今天恰好在这里看到,有个古老的页面刷新技术,不管页面是否可见都能刷新: 标签配合 refresh 属性。缓存
简单介绍下这个 meta 头。假如页面 区有下面这行代码,页面会每隔 600s 刷新一次。bash
<metahttp-equiv="refresh"content="600">
复制代码
直接刷整个页面固然可让页面更新,但也会把页面当前状态刷掉,还浪费流量,体验并很差。咱们能够在页面引入 iframe,每次只刷新 iframe。这个 iframe 很小,流量是省了,可是每次都刷新仍是会浪费 HTTP 请求,用强缓存又会使得更新很麻烦。有没有更好的办法呢?iphone
这时候,轮到 Data URI 出场了。咱们直接将含有 meta 刷新的页面 URL 编码,以 Data URI 的格式放到 iframe 的 src 中,就不会产生请求了。看下实际效果截图:post
这个 Demo 的代码以下,iframe 跟主页面没有跨域,怎么传递消息都行。考虑本文讨论的是高级浏览器,我直接用的 postMessage:测试
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta content="width=320,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" />
<title>后台页面更新测试 - meta refresh</title>
</head>
<body>
<div id="test" style="font-size:32px;"></div>
<script>
var count = 0,
test = document.getElementById('test');
window.addEventListener('message', function(e) {
if(e.data === 'refresh') {
count++;
test.innerHTML = count;
document.title = count;
}
}, false);
var duration = 1; /* 1s */
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'data:text/html,%3C%21DOCTYPE%20html%3E%0A%3Chtml%3E%0A%3Chead%3E%0A%09%3Cmeta%20charset%3D%22utf-8%22%20%2F%3E%0A%09%3Cmeta%20http-equiv%3D%22refresh%22%20content%3D%22'+ duration +'%22%20id%3D%22metarefresh%22%20%2F%3E%0A%09%3Ctitle%3Ex%3C%2Ftitle%3E%0A%3C%2Fhead%3E%0A%3Cbody%3E%0A%09%3Cscript%3Etop.postMessage%28%27refresh%27%2C%20%27%2A%27%29%3B%3C%2Fscript%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E';
document.body.insertBefore(iframe, document.body.childNodes[0]);
</script>
</body>
</html>
复制代码
data:text/html,<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="refresh" content="' duration '" id="metarefresh" />
<title>x</title>
</head>
<body>
<script>top.postMessage('refresh', '*');</script>
</body>
</html>' 复制代码
在装有 iOS7 的 iPhone 上测试,也没问题。只是 iPhone 的 Safari 在预览多标签的时候,显示的是页面的截图,因此下图中虽然整个页面一直在更新,却只能看到 Title 的变化。
实际上,Chrome PC 版支持 这样的小于 1 的刷新间隔,因此这种方案也可让 Chrome PC 版不可见页面的定时器间隔小于 1s。只是浏览器的刷新按钮会闪个不停,估计没人忍受得了。
最后,在 iOS 中,使用 方案模拟的定时器,若是须要仅在 Tab 未激活时才刷新,也很简单。代码以下:
<meta http-equiv="refresh" content="10" id="refresh">
<script>
var meta = document.getElementById("refresh");
setInterval(function() {
meta.content = meta.content;
}, parseInt(meta.content / 2) * 1000);
</script>
复制代码
首先获取 meta 的刷新间隔,再设置一个间隔稍短的定时器,不断重置 meta 的刷新间隔。这样只要页面处于激活状态,meta 就没机会刷新。而页面非激活时,因为 setInterval 被冻结,页面又能够被 meta 刷新了。
PS:本文讨论的只是技术问题,不表明我支持这么用。这种方案的可用性 / 实用性请你们自行判断。