JavaScript 实现倒计时抢购

816 阅读1分钟

思考核心问题

倒计时时间的获取

客户端

  • 当前时间获取客户端本地的时间可能会造成多次抢购的问题
  • 原因:客户端本地时间是可以自己任意修改的 服务器端
  • 时间必须从服务器端获取服务器的时间
  • 弊端:会存在时间偏差的问题
  • 优化:
    • 使用 HEAD 方式请求:只获取服务器的响应头信息,不需要获取响应主体信息
    • 当我们监听到 AJAX 请求转状态为 2 时就可以了,服务器返回时间直接在响应头信息中获取

每隔一秒后,时间的获取

  • 在页面不刷新的情况下,每间隔 1 秒,不在从服务器获取时间,如果从服务器获取,时间误差会越来越大,服务器也会崩溃
  • 所以,我们可以基于第一次获取的结果,手动累加 1000ms 即可
// 倒计时抢购
let countdownModule = (function () {
  let textBox = document.querySelector('.text'),
      serverTime = 0,
      targetTime = +new Date('2020/12/05 16:00:00'),
      timer = null;

  // 获取服务器时间
  const queryServerTime = function queryServerTime() {
      return new Promise(resolve => {
          let xhr = new XMLHttpRequest;
          xhr.open('HEAD', '/');
          xhr.onreadystatechange = () => {
              if ((xhr.status >= 200 && xhr.status < 300) && xhr.readyState === 2) {
                  let time = xhr.getResponseHeader('Date');
                  // 获取的时间是格林尼治时间 -> 变为北京时间
                  resolve(+new Date(time));
              }
          };
          xhr.send(null);
      });
  };

  // 倒计时计算
  const supplyZero = function supplyZero(val) {
      val = +val || 0;
      return val < 10 ? `0${val}` : val;
  };
  const computed = function computed() {
      let diff = targetTime - serverTime,
          hours = 0,
          minutes = 0,
          seconds = 0;

      // 如果小于 0,那么抢购时间开始
      if (diff <= 0) {
          // 到达抢购时间了
          textBox.innerHTML = '00:00:00';
          // 清除定时器
          clearInterval(timer);
          return;
      }
      // 没到时间则计算即可
      // 剩余时间中先计算出小时的毫秒数,然后在转换成小时
      hours = Math.floor(diff / (1000 * 60 * 60));
      // 用剩余毫秒数-计算出的小时的毫秒数,剩下的就是分钟毫秒数
      diff = diff - hours * 1000 * 60 * 60;
      // 分钟毫秒数转换为分钟
      minutes = Math.floor(diff / (1000 * 60));
      // 剩下的毫秒数 - 完整的分钟所占用的毫秒数,剩下的就是秒的毫秒数
      diff = diff - minutes * 1000 * 60;
      // 秒的毫秒数转换成秒
      seconds = Math.floor(diff / 1000);
      // 页面显示
      textBox.innerHTML = `${supplyZero(hours)}:${supplyZero(minutes)}:${supplyZero(seconds)}`;
  };

  return {
      async init() {
          serverTime = await queryServerTime();
          computed();

          // 设置定时器   
          timer = setInterval(() => {
              serverTime += 1000;
              computed();
          }, 1000);
      }
  };
})();
countdownModule.init();