(轉(zhuǎn))JS實現(xiàn)活動精確倒計時

背景

前端頁面倒計時功能在很多場景中會用到,如運營活動開始倒計時和活動結(jié)束倒計時鳍悠,又如購物網(wǎng)站的秒殺倒計時税娜,搶購倒計時,還有我們手Q春節(jié)搶紅包倒計時等等……. 最近的話費代付項目中藏研,也涉及倒計時功能敬矩,但在開發(fā)過程中遇到一些麻煩和坑點,下面和大家分享一下最后是如何解決的蠢挡。

坑點

手Q春節(jié)搶明星紅包活動弧岳,就有產(chǎn)品吐槽兩個手機在不同時間點打開同一個活動顯示的開搶倒計時不一樣,誤差大的甚至相差幾分鐘业踏,導(dǎo)致某些用戶在活動顯示還未開始紅包就已被搶完了禽炬。為什么誤差會這么大呢?

對于這個問題堡称,前臺開發(fā)同學(xué)一般會猜測是這個原因:倒計時讀取了客戶端時間造成的瞎抛,因為客戶端時間和服務(wù)端時間有誤差,應(yīng)該讀取服務(wù)器時間却紧。

是的桐臊,倒計時不應(yīng)該讀取客戶端時間,客戶端時間用戶可以隨時調(diào)整晓殊,會造成不一致断凶,應(yīng)讀取服務(wù)器返回時間。但實踐證明巫俺,做了這一步還未夠认烁,頁面運行時間長了,新開的頁面和原打開頁面還是存在誤差。

京東團購也存在這個問題:

造成誤差的原因主要有幾種可能:

1. 沒有考慮js凍結(jié)運行耗費時間却嗡;(特別是移動端容易出現(xiàn)舶沛,下滑頁面時倒計時不動了)

2. 沒有考慮頁面渲染和函數(shù)運行累積時間;(京東的誤差貌似屬于這種)

3. 其他代碼邏輯問題(這種情況就復(fù)雜了窗价,這里不討論)如庭;

計時器原理

倒計時功能離不開setTimeout或setInterval這兩個函數(shù),要用好這兩個函數(shù)必先了解好Javascript解釋器的工作原理撼港,前端界大牛John.Resig (jQuery作者) 有篇文章很好講解了Javascript解釋器工作原理 — 《How JavaScript Timers Work》坪它。

前端開發(fā)同學(xué)都知道,javascript是單線程的(web worker除外)帝牡,更好理解的解釋是javascript解釋器是單線程工作往毡,它不能在處理一個ajax的callback的同時去處理click event的callback,而是必須按照先后隊列順序執(zhí)行靶溜。

這圖包含信息量很大开瞭,這里按照自己的理解描述一下:

這圖從上往下看,垂直方向是時間罩息,以ms為單位惩阶,藍色模塊是執(zhí)行代碼所占的時間段,如第一個代碼模塊執(zhí)行js占用了約18ms, 第二個模塊執(zhí)行js占用了約11ms扣汪,其他模塊類似。由于js是單線程執(zhí)行锨匆,同一時間只能執(zhí)行一個js代碼(同一時間其他異步事件執(zhí)行會被阻塞 ) , 當(dāng)異步事件發(fā)生時崭别,它會進入代碼執(zhí)行隊列,執(zhí)行線程空閑時依照隊列順序依次執(zhí)行代碼恐锣。

第一個模塊初始化了兩個定時器茅主,一個10ms延遲的setTimeout和10ms的setInterval。這些定時器可能會在我們第一個代碼塊執(zhí)行結(jié)束之前就觸發(fā)土榴,這取決于定時器在第一個代碼塊中啟動的位置和時間诀姚。注意,定時器雖然觸發(fā)了玷禽,但是并不會立即執(zhí)行赫段,它只是把需要延遲執(zhí)行的函數(shù)按時間先后加入了執(zhí)行隊列,在線程的某一個空閑的時間點矢赁,這個函數(shù)就能夠得到執(zhí)行糯笙。

按照第一個模塊事件觸發(fā)的順序(Mouse Click Occurs -. 10ms Timer Fires),第一個模塊代碼執(zhí)行結(jié)束后撩银,按照隊列中等待的先后順序執(zhí)行事件给涕,先執(zhí)行Mouse Click CallBack再執(zhí)行Timer。在執(zhí)行Mouse Click CallBack模塊時,Interval第一次觸發(fā)未執(zhí)行加入隊列够庙。在執(zhí)行Timer模塊時恭应,Interval第二次觸發(fā)未執(zhí)行加入隊列。待Mouse Click CallBack和Timer模塊都執(zhí)行完畢后耘眨,再依次執(zhí)行隊列中已觸發(fā)的Interval事件昼榛。后面模塊由于沒有阻塞的事件了,所以按照既定10ms執(zhí)行Interval事件毅桃。

倒計時問題

如果上面Javascript計時器原理理解了褒纲,就很好明白倒計時功能存在問題的隱患。

先看一段測試代碼:

var  start  =  new  Date().getTime();

var  count  =  0;

//定時器測試

setInterval(function(){

 count++;

 console.log(  new  Date().getTime()  -  (start  +  count *  1000));

},1000);

目測代碼就知道運行結(jié)果钥飞,定時器每秒執(zhí)行一次莺掠,每次輸出應(yīng)該是0 。

實際輸出:

結(jié)論:由于代碼執(zhí)行占用時間和其他事件阻塞原因读宙,導(dǎo)致有些事件執(zhí)行延遲了幾ms彻秆,但影響很微。

下面加一段阻塞代碼看看:


var  start  =  new  Date().getTime();

var  count  =  0;

//占用線程事件

setInterval(function(){

 var  j  =  0;

 while(j++  <  100000000);

},  0);

//定時器測試

setInterval(function(){

 count++;

 console.log(  new  Date().getTime()  -  (start  +  count *  1000));

},1000);

實際輸出:

結(jié)論:由于加了很占線程的阻塞事件结闸,導(dǎo)致定時器事件每次執(zhí)行延遲越來越嚴(yán)重唇兑。

由于實際項目中,執(zhí)行計時器的同時桦锄,會有很多其他異步阻塞事件扎附,會導(dǎo)致倒計時功能不精確。

解決思路

這里先分析一下從獲取服務(wù)器時間到前端顯示倒計時的過程:

1. 客戶端http請求服務(wù)器時間结耀;

2. 服務(wù)器響應(yīng)完成留夜;

3. 服務(wù)器通過網(wǎng)絡(luò)傳輸時間數(shù)據(jù)到客戶端;

4. 客戶端根據(jù)活動開始時間和服務(wù)器時間差做倒計時顯示图甜;

服務(wù)器響應(yīng)完成的時間其實就是服務(wù)器時間碍粥,但經(jīng)過網(wǎng)絡(luò)傳輸這一步,就會產(chǎn)生誤差了黑毅,誤差大小視網(wǎng)絡(luò)環(huán)境而異嚼摩,這部分時間前端也沒有什么好辦法計算出來,一般是幾十ms以內(nèi)矿瘦,大的可能有幾百ms枕面。

可以得出:當(dāng)前服務(wù)器時間 = 服務(wù)器系統(tǒng)返回時間 + 網(wǎng)絡(luò)傳輸時間 + 前端渲染時間 + 常量(可選),這里重點是說要考慮前端渲染的時間匪凡,避免不同瀏覽器渲染快慢差異造成明顯的時間不同步膊畴,這是第一點。(網(wǎng)絡(luò)傳輸時間忽略或加個常量唄)

獲得服務(wù)器時間后病游,前端進入倒計時計算和計時器顯示唇跨,這步就要考慮js代碼凍結(jié)和線程阻塞造成計時器延時問題了稠通,我的思路是通過引入計數(shù)器,判斷計時器延遲執(zhí)行的時間來調(diào)整买猖,盡量讓誤差縮小改橘,不同瀏覽器不同時間段打開頁面倒計時誤差可控制在1s以內(nèi)

關(guān)鍵實現(xiàn)代碼如下:

//繼續(xù)線程占用

setInterval(function(){

 var  j  =  0;

 while(j++  <  100000000);

},  0);

//倒計時

var  interval  =  1000,

 ms  =  50000,  //從服務(wù)器和活動開始時間計算出的時間差玉控,這里測試用50000ms

 count  =  0,

 startTime  =  new  Date().getTime();

if(  ms  >=  0){

 var  timeCounter  =  setTimeout(countDownStart,interval);                  

}

function  countDownStart(){

 count++;

 var  offset  =  new  Date().getTime()  -  (startTime  +  count *  interval);

 var  nextTime  =  interval  -  offset;

 var  daytohour  =  0;

 if  (nextTime  <  0)  {  nextTime  =  0  };

 ms  -=  interval;

 console.log("誤差:"  +  offset  +  "ms飞主,下一次執(zhí)行:"  +  nextTime  +  "ms后,離活動開始還有:"  +  ms  +  "ms");

 if(ms  <  0){

              clearTimeout(timeCounter);

 }else{

              timeCounter  =  setTimeout(countDownStart,nextTime);

 }

}

運行結(jié)果:

結(jié)論:由于線程阻塞延遲問題高诺,做了setTimeout執(zhí)行時間的誤差修正碌识,保證setTimeout執(zhí)行時間一致。若凍結(jié)時間特別長的虱而,還要做特殊處理筏餐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市牡拇,隨后出現(xiàn)的幾起案子魁瞪,更是在濱河造成了極大的恐慌草雕,老刑警劉巖彤断,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驳概,居然都是意外死亡剔蹋,警方通過查閱死者的電腦和手機旅薄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泣崩,“玉大人赋秀,你說我怎么就攤上這事÷上耄” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵绍弟,是天一觀的道長技即。 經(jīng)常有香客問我,道長樟遣,這世上最難降的妖魔是什么而叼? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮豹悬,結(jié)果婚禮上葵陵,老公的妹妹穿的比我還像新娘。我一直安慰自己瞻佛,他們只是感情好脱篙,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布娇钱。 她就那樣靜靜地躺著,像睡著了一般绊困。 火紅的嫁衣襯著肌膚如雪文搂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天秤朗,我揣著相機與錄音煤蹭,去河邊找鬼。 笑死取视,一個胖子當(dāng)著我的面吹牛硝皂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播作谭,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼稽物,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丢早?” 一聲冷哼從身側(cè)響起姨裸,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怨酝,沒想到半個月后傀缩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡农猬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年赡艰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斤葱。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡慷垮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揍堕,到底是詐尸還是另有隱情料身,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布衩茸,位于F島的核電站芹血,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏楞慈。R本人自食惡果不足惜幔烛,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望囊蓝。 院中可真熱鬧饿悬,春花似錦、人聲如沸聚霜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至傲宜,卻和暖如春运杭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背函卒。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工辆憔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人报嵌。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓虱咧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锚国。 傳聞我的和親對象是個殘疾皇子腕巡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容