倒計時是網頁中最多見的一種功能,好比淘寶雙十必定時搶購、小米手機定時搶購等,這些都是倒計時的常見場景。倒計時也是前端初學者必學的一個demo,正是因爲倒計時功能的常見性,致使一些問題經常被忽略,好比:html
也許在你剛開始學習前端,寫倒計時的時候,並無考慮上面的問題,但在真正的業務場景中,上面的問題會影響到倒計時的準確性的,是不容忽略的。前端
活動頁面中須要實現一個倒計時搶票功能,當北京時間爲 2025/12/12 00:00:00 的時候,頁面「當即搶票」按鈕可點擊。後端
看到上述場景,咱們通常會想到下面的常規寫法:瀏覽器
let target = Date.parse('2025/12/12 00:00:00');
let now = Date.now();
if(target <= now) {
console.log('按鈕可點擊')
} else {
console.log('按鈕不可點擊')
}
複製代碼
上面代碼的核心思想:利用當前本地時間和目標時間比較。緩存
上面代碼看似正確,但沒有考慮跨時區問題。北京時間到達了目標時間2025/12/12 00:00:00
,因爲不一樣的時區存在時差,其餘時區有可能尚未到,因此按照代碼邏輯,不一樣時區的用戶有的能夠點擊搶票按鈕,有的不能夠,失去了公平性、同時性。安全
下面對時區的相關概念講解一下:bash
時區服務器
時區是地球上的區域使用同一個時間定義。之前,人們經過觀察太陽的位置(時角)決定時間,這就使得不一樣經度的地方的時間有所不一樣(地方時)。1863年,首次使用時區的概念。時區經過設立一個區域的標準時間部分地解決了這個問題。性能
地球是自西向東自轉,東邊比西邊先看到太陽,東邊的時間也比西邊的早。地球自轉一週是24小時,因此劃分爲24個時區,即東1—12區,西1—12區,相鄰兩個時區的時間相差1小時學習
例如,中國東8區的時間總比泰國東7區的時間早1小時,而比日本東9區的時間晚1小時。所以,出國旅行的人,必須隨時調整本身的手錶,才能和當地時間相一致。凡向西走,每過一個時區,就要把表撥慢1小時(好比2點撥到1點);凡向東走,每過一個時區,就要把表撥快1小時(好比1點撥到2點)。而且規定英國(格林尼治天文臺舊址)爲本初子午線,即零度經線
格林威治時間
格林威治子午線上的地方時,或零時區(中時區)的區時叫作格林威治時間,也叫世界時。(更多詳細的概念不說了,這裏咱們不須要。) 好比咱們中國是東八區,北京時間是(GMT+08:00)
本地與格林威治時間的時差:
時差 = new Date().getTimezoneOffset(); // 單位是分鐘
複製代碼
已知格林威治時間,換算本地正確時間:
本地時間 = 格林威治時間 - 時差
已知本地時間,換算對應格林威治時間:
格林威治時間 = 本地時間 + 時差
已知本地時間,換算其餘時區的時間:
由於時區間的差別是以小時爲單位的。因此算出0時區的時間後,再減去或加上相應的小時便可(東N區便+N小時,西N區便-N小時)。爲了方便計算,東N區記作正數,西N區記作負數。
目標時區時間 = 本地時間 + 時差 + 時區間隔
因此上面的業務場景,咱們就能夠把本地時間轉換爲東八區的北京時間,本地時間和目標倒計時時間都是東八區的時間,二者就能夠進行比較判斷了。
let target = Date.parse('2025/12/12 00:00:00');
let now = getNowDate(8); // 將本地時間轉換爲東8區的時間
if(target <= now) {
console.log('按鈕可點擊')
} else {
console.log('按鈕不可點擊')
}
function getNowDate(timeZone) {
var timezone = timeZone || 8; //目標時區時間,東八區
// 本地時間和格林威治的時間差,單位爲分鐘
var offset_GMT = new Date().getTimezoneOffset();
// 本地時間距 1970 年 1 月 1 日午夜(GMT 時間)之間的毫秒數
var nowDate = new Date().getTime();
var targetDate = nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000;
return targetDate;
}
複製代碼
因爲人爲設置的緣由,用戶的本地時間,有可能不許確。要想保證倒計時的精確性,通常想到的方法是依賴後端接口,其實不依賴後端接口也能夠保證倒計時精準,下面介紹下這兩種方法:
1. 服務端接口返回當前時間戳
let targetTime = Date.parse('2025/12/12 00:00:00');
let serverTime = getServerTime(); // 請求服務端接口,返回服務器當前時間戳
let localTime = getNowDate(8); // 用戶本地時間戳
let timeOff = serverTime - localTime;
let rightTargetTime = targetTime - timeOff; // 去除誤差後的目標時間
if(rightTargetTime <= localTime) {
console.log('按鈕可點擊')
} else {
console.log('按鈕不可點擊')
}
function getNowDate(timeZone) {
var timezone = timeZone || 8; //目標時區時間,東八區
// 本地時間和格林威治的時間差,單位爲分鐘
var offset_GMT = new Date().getTimezoneOffset();
// 本地時間距 1970 年 1 月 1 日午夜(GMT 時間)之間的毫秒數
var nowDate = new Date().getTime();
var targetDate = nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000;
return targetDate;
}
複製代碼
核心思想:藉助服務器接口返回正確的本地時間,而後和用戶本地時間做比較,求出誤差值,根據誤差值計算出正確的目標時間。
注意: serverTime
返回的是服務器時間,服務器部署在哪一個時區,返回的就是哪一個時區的時間,因此要確保返回的也是東八區才行。
2. Head請求獲取服務器時間戳
Head 請求
HEAD方法跟GET方法相同,只不過服務器響應時不會返回消息體。一個HEAD請求的響應中,HTTP頭中包含的元信息應該和一個GET請求的響應消息相同。這種方法能夠用來獲取請求中隱含的元信息,而不用傳輸實體自己。也常常用來測試超連接的有效性、可用性和最近的修改。
一個HEAD請求的響應可被緩存,也就是說,響應中的信息可能用來更新以前緩存的實體。若是當前實體跟緩存實體的閾值不一樣(可經過Content-Length、Content-MD五、ETag或Last-Modified的變化來代表),那麼這個緩存就被視爲過時了。
HEAD請求經常被忽略,可是能提供不少有用的信息,特別是在有限的速度和帶寬下。主要有如下特色:
如何使用Head請求獲取服務器時間戳?
每一個get請求,response header
響應頭信息中都會返回當前服務器對應的零時區時間。
每一個頁面都會有html文檔,這個也屬於get請求,以下圖所示:
咱們能夠利用Head請求,拿到這個date頭信息:
var xhr = new window.XMLHttpRequest;
xhr.responseType = "document";
// 經過get的方式請求當前文件
xhr.open("head", location.href);
xhr.send(null);
// 監聽請求狀態變化
xhr.onreadystatechange = function () {
var time = null,
curDate = null;
if (xhr.readyState === 2) {
// 獲取響應頭裏的時間戳
time = xhr.getResponseHeader("Date");
}
};
複製代碼
獲得的time
是服務器對應的零時區的時間,經過下面代碼能夠轉換爲用戶當前所在時區的時間:
new Date(time);
複製代碼
因此倒計時代碼就能夠改寫爲:
var xhr = new window.XMLHttpRequest;
xhr.responseType = "document";
// 經過get的方式請求當前文件
xhr.open("head", location.href);
xhr.send(null);
// 監聽請求狀態變化
xhr.onreadystatechange = function () {
var time = null,
curDate = null;
if (xhr.readyState === 2) {
// 獲取響應頭裏的時間戳
time = xhr.getResponseHeader("Date");
countDown(new Date(time).getTime());
}
};
function countDown(time) {
let targetTime = Date.parse('2025/12/12 00:00:00');
let serverTime = getNowDate(time, 8); // Head請求,返回服務器當前時間戳
let localTime = getNowDate(Date.now(), 8); // 用戶本地時間戳
let timeOff = serverTime - localTime;
let rightTargetTime = targetTime - timeOff; // 去除誤差後的目標時間
if(rightTargetTime <= localTime) {
console.log('按鈕可點擊')
} else {
console.log('按鈕不可點擊')
}
}
function getNowDate(localTime, timeZone) {
var timezone = timeZone || 8; //目標時區時間,東八區
// 本地時間和格林威治的時間差,單位爲分鐘
var offset_GMT = new Date().getTimezoneOffset();
// 本地時間距 1970 年 1 月 1 日午夜(GMT 時間)之間的毫秒數
var nowDate = localTime;
var targetDate = nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000;
return targetDate;
}
複製代碼
這種方法相比第一種有以下優勢:
上面的第二種方法就是咱們最終想要的,前端能夠不依賴後端,實現一個支持跨時區、兼容本地時間不許的倒計時了。
掃一掃 關注個人公衆號【前端名獅】,更多精彩內容陪伴你!