其實這個標題略微有點標題黨: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:本文討論的只是技術問題,不表明我支持這麼用。這種方案的可用性 / 實用性請你們自行判斷。