论巨多计时器时,页面自我救赎之路

931 阅读2分钟

论巨多计时器时,页面自我救赎之路

场景

一个页面存在多个倒计时功能,或者一个项目中存在多个计时同步功能.
一般搞活动的产品贼勾八喜欢这玩意

image.png

Vue撸起

搞起

  1. 首先定义个函数,其中实现 倒计时+cancel倒计时+添加目标时间段+删除目标时间段+更新视图功能

定义一个 overallCoreTimerMeter 类,使用window.requestAnimationFrame进行ui重绘执行,setTimeout进行兜底。


class overallCoreTimerMeter {

   private static instance: overallCoreTimerMeter;
  /**
   * 全局懒加载单例
   */
  public static getInstance(endTime?: Time) {
    if (!overallCoreTimerMeter.instance) {
      overallCoreTimerMeter.instance = new overallCoreTimerMeter(endTime);
    }
    return overallCoreTimerMeter.instance;
  }
  /**
   * 需要倒计时的集合
   */
  list: TimeUkeyItem[] = [];
  /**
   * 中央计数器的结束时间
   */
  endTime?: number;
  /**
   * 计时器的return值
   */
  cancelNumber?: number;

  constructor(endTime?: Time) {
    // 判断是否有自动结束功能
    endTime && !dayjs(endTime).isValid() && (endTime = undefined);

    this.endTime = endTime ? dayjs(endTime).valueOf() : (endTime as undefined);
    //   兼容
    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = setTimeout;
      window.cancelAnimationFrame = clearTimeout;
    }
  }
}
  1. 新增目标时间节点功能,对于目标时间节点默认转化成时间戳,方便下面的计算操作

/**
   * 新增倒计时
   */
  addMeter(item: AfferentTime) {
    if (dayjs(item.endTime).isValid() && dayjs(item.endTime).valueOf() > Date.now()) {
      item.endTime = dayjs(item.endTime).valueOf();
      // 进行排序,从时间从小到大排序
      this.list.push(item);
      const itemCtn = item as TimeUkeyItem;
      // 默认上一次的区间值
      itemCtn.beforeTime = Date.now();
      // 设置默认时间间隔
      if (typeof itemCtn.rate !== "number") {
        itemCtn.rate = 1000;
      }
      // 开始计时器 (从0到1)
      if (this.list.length === 1) this.startTimerMeterControl();
      return new Promise((resolve) => (itemCtn._resolve = resolve));
    }
    throw new Error("请传入key,符合时间的对象");
  }

  1. 删除目标节点功能,对于达到目标节点去除

/**
   * 删除已经达到预期时间
   */
  private deleteItem(currtTime: number) {
    const { list } = this;
    this.list = list.reduce<TimeUkeyItem[]>((reduce, item) => {
      if (item.endTime <= currtTime) {
        //  删除前调用callback
        item.callback?.();
        item._resolve?.();
        return reduce;
      }
      return reduce.concat(item);
    }, []);
  }

4.更新视图


/**
   * 获取需要更新值的list
   */
  private getUpdateList(currtTime: number) {
    this.list.forEach((item) => {
      if (item.rate === 0 || currtTime - item.beforeTime! >= item.rate!) {
        // 更新了
        item.beforeTime = currtTime;
        this.updateTheView(currtTime, item);
      }
    });
  }
  /**
   * 更新视图
   */
  private updateTheView(currtTime: number, item: TimeUkeyItem) {
  // 相差的时间集合
    const list = timeGapNumber((item.endTime as number) - currtTime);
    // 更新
    item.viewRefs.value = list;
  }
  

5.设置启动中央计时器功能,其中启动删除的控制功能在内容消化


/**
   * 时间计时器
   */
  private startTimerMeterControl() {
    if (!this.list.length) {
      this.cancelAccount();
    }
    //   重置上次的,新增
    window.cancelAnimationFrame(this.cancelNumber!);
    // 开始循环逻辑内容
    const currtTime = Date.now();
    // 更新视图
    this.getUpdateList(currtTime);
    // 删除过期的
    this.deleteItem(currtTime);
    // 判断中央计时器是否结束
    if (this.endTime && currtTime >= this.endTime) {
      this.cancelAccount();
      return;
    }
    this.cancelNumber = window.requestAnimationFrame(this.startTimerMeterControl);
  }