如何做毫秒级倒计时页面加载效果?

66 阅读2分钟

20231219_131129.gif

view部分

    <div class="endtime-custom2">
        距离竞价截止时间
        <span v-if="count.showCountDown">
          <count-down class="endtime-custom" ref="countDown" :time="count.time" millisecond format="HH:mm:ss:SS" :autoStart="false"></count-down>
        </span>
        <span v-else>{{count.title}}</span>
      </div>

JS部分

import countDown from '@/components/pms/countDown/countDown'
   components: {
      countDown
    },
    data () {
      return {
        count:{
          time: 60 *60 * 1000 * 24,
          showCountDown:false,
          title:'竞价已结束'
          }
        }
     }     

在components中定义自定义组件新建 countDown/countDown.vue文件

  <div :class="bem()">
    <slot v-bind:row="timeData">{{ formattedTime }}</slot>
  </div>
</template>

<script>
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;

function parseTimeData(time) {
  const days = Math.floor(time / DAY);
  const hours = Math.floor(time % DAY / HOUR);
  const minutes = Math.floor(time % HOUR / MINUTE);
  const seconds = Math.floor(time % MINUTE / SECOND);
  const milliseconds = Math.floor(time % SECOND);
  return { days, hours, minutes, seconds, milliseconds };
}
function parseFormat(format, timeData) {
  var days = timeData.days;
  var hours = timeData.hours,
      minutes = timeData.minutes,
      seconds = timeData.seconds,
      milliseconds = timeData.milliseconds;

  if (format.indexOf('DD') === -1) {
    hours += days * 24;
  } else {
    format = format.replace('DD', padZero(days));
  }

  if (format.indexOf('HH') === -1) {
    minutes += hours * 60;
  } else {
    format = format.replace('HH', padZero(hours));
  }

  if (format.indexOf('mm') === -1) {
    seconds += minutes * 60;
  } else {
    format = format.replace('mm', padZero(minutes));
  }

  if (format.indexOf('ss') === -1) {
    milliseconds += seconds * 1000;
  } else {
    format = format.replace('ss', padZero(seconds));
  }

  if (format.indexOf('S') !== -1) {
    var ms = padZero(milliseconds, 3);

    if (format.indexOf('SSS') !== -1) {
      format = format.replace('SSS', ms);
    } else if (format.indexOf('SS') !== -1) {
      format = format.replace('SS', ms.slice(0, 2));
    } else {
      format = format.replace('S', ms.charAt(0));
    }
  }

  return format;
}
function isSameSecond(t1, t2) {
  return Math.floor(t1 / 1000) === Math.floor(t2 / 1000);
}
function padZero(num, targetLength) {
  if (targetLength === void 0) {
    targetLength = 2;
  }

  var str = num + '';

  while (str.length < targetLength) {
    str = '0' + str;
  }

  return str;
}
const raf = fn => window.requestAnimationFrame.call(window, fn);
const cancelRaf = id => window.cancelAnimationFrame.call(window, id);
export default {
  name: 'count-down',
  data() {
    return {
      remain: 0
    };
  },
  props: {
    millisecond: Boolean, // 毫秒级渲染
    time: { type: [Number, String], default: 0 },
    format: { type: String, default: 'HH:mm:ss' },
    autoStart: { type: Boolean, default: true }
  },
  activated() {
    if (this.keepAlivePaused) {
      this.counting = true;
      this.keepAlivePaused = false;
      this.tick();
    }
  },
  methods: {
    start() {
      if (this.counting) return;
      this.counting = true;
      this.endTime = Date.now() + this.remain;
      this.tick();
    },
    pause() {
      this.counting = false;
      cancelRaf(this.rafId);
    },
    reset() {
      this.pause();
      this.remain = +this.time;
      if (this.autoStart) {
        this.start();
      }
    },
    tick() {
      if (!(typeof window !== 'undefined')) return;
      if (this.millisecond) {
        this.microTick();
      } else {
        this.macroTick();
      }
    },
    microTick() {
      this.rafId = raf(() => {
        /* istanbul ignore if */
        // 如果呼叫结束后立即重置
        if (!this.counting) return;
        this.setRemain(this.getRemain());
        if (this.remain > 0) {
          this.microTick();
        }
      });
    },
    macroTick() {
      this.rafId = raf(() => {
        /* istanbul ignore if */
        if (!this.counting) return;// 如果呼叫结束后立即重置
        var remain = this.getRemain();

        if (!isSameSecond(remain, this.remain) || remain === 0) {
          this.setRemain(remain);
        }

        if (this.remain > 0) {
          this.macroTick();
        }
      });
    },
    getRemain() {
      return Math.max(this.endTime - Date.now(), 0);
    },
    setRemain(remain) {
      this.remain = remain;
      this.$emit('change', this.timeData);

      if (remain === 0) {
        this.pause();
        this.$emit('finish');
      }
    },
    createBem(name) {
      /**
       * bem helper
       * b() // 'button'
       * b('text') // 'button__text'
       * b({ disabled }) // 'button button--disabled'
       * b('text', { disabled }) // 'button__text button__text--disabled'
       * b(['disabled', 'primary']) // 'button button--disabled button--primary'
       */
      return function (el, mods) {
        function gen(name, mods) {
          if (!mods) return '';
          if (typeof mods === 'string') return " " + name + "--" + mods;
          if (Array.isArray(mods)) return mods.reduce((ret, item) => ret + gen(name, item), '');
          return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? gen(name, key) : ''), '');
        }

        if (el && typeof el !== 'string') {
          mods = el; el = '';
        }

        el = el ? name + "__" + el : name;
        return "" + el + gen(el, mods);
      };
    }
  },
  computed: {
    timeData() {
      return parseTimeData(this.remain);
    },
    formattedTime() {
      return parseFormat(this.format, this.timeData);
    },
    bem() {
      return (...cls) => this.createBem(this.$options.name)(...cls);
    }
  },
  watch: {
    time: {
      immediate: true,
      handler: 'reset'
    }
  },
  deactivated() {
    if (this.counting) {
      this.pause();
      this.keepAlivePaused = true;
    }
  },
  beforeDestroy() {
    this.pause();
  },
}

</script>

<style scoped>
.count-down {
  color: #323233;
  font-size: 14px;
  line-height: 20px;
}
</style>