setInterval定时器封装(settimeout\requestAnimationFrame),代码抽离,浏览器切换后定时器停止执行

需求:

点击按钮,请求成功返回后,置灰60秒倒计时,不允许点击

碰到问题:

1、页面有个loading,在loading为true时,加载超过1秒后,计数不连贯

2、代码写到一个文件中,耦合严重

3、浏览器切换后定时器停止执行,浏览器切换tab页面后,切换回去,仍有计数,并停止

解决方法

1、在loading结束后,再开始计数

2、抽离代码,模块化

3、浏览器监听事件 document.addEventListener(‘visibilitychange’,()=>{})

浏览器标签页被隐藏或显示的时候会触发visibilitychange事件。

页面代码实现
vue文件
        <u-button
          :disabled="store.state.timer.remainTime > 0"
          :loading="triggerLoading"
          type="primary"
          @click="handleTriggerTaskUploadSyncReportJob"
        >
          <template v-if="store.state.timer.remainTime > 0">
            <u-icon type="time" />
            {{ store.state.timer.remainTime + ' ' }}
          </template>
          同步记录
        </u-button>
        

        
        
        import { useStore } from 'vuex';
        import { useRouter, onBeforeRouteLeave } from 'vue-router';
        import {
          triggerTaskUploadSyncReportJobApi
        } from '@/api/statistics';
        import visibility from '@/mixins/visibility';
        import { createTimer, removeTimer } from './hooks/createTimer';
        
        
        mixins: [visibility],
    // #region 同步记录按钮
    // 同步请求,接口同步数据结束后,同步成功返回true
    // 同步返回后,执行请求列表接口,查询列表
    // 60秒内,禁止点击再次点击同步记录
    // 页面离开时,关闭定时器
    // 页面刷新后,如果存在页面剩余刷新时间,则重新启动定时器
    const triggerLoading = ref(false); // 同步记录加载状态

    // 退出页面时,关闭定时器
    onBeforeRouteLeave(() => removeTimer());
    // #endregion 同步记录
    
    
    if (triggerLoading.value) {
      createTimer(true); // 主动(true)创建定时器
      triggerLoading.value = false;
    }
    
    // 同步记录
    const handleTriggerTaskUploadSyncReportJob = () => {
      triggerLoading.value = true;
      triggerTaskUploadSyncReportJobApi({ uploadDate: parseTime(new Date(), 'YYYY-MM-DD') }).then(
        res => {
          if (res) {
            getList(); // 刷新列表
          }
        }
      );
    };
    
    onMounted(() => {
      getList();
      createTimer(false); // 被动(false)创建定时器
    });
    
    return {
      triggerLoading, // 同步记录按钮
      handleTriggerTaskUploadSyncReportJob,
      store // 剩余时间
    };
createTimer.js-创建定时器
// 同步记录按钮
// 同步请求,接口同步数据结束后,同步成功返回true
// 同步返回后,执行请求列表接口,查询列表
// 主动启动定时器,60秒内,禁止点击再次点击同步记录
// 页面离开时,关闭定时器
// 页面刷新后,如果存在页面剩余时间,则重新被动启动定时器
import store from '@/store';

import { setInterValCustom, cancleInterValCustom } from './timer';

// 生成剩余时间-60秒内静止点击
export const generateRemainTime = () => {
  if (localStorage.getItem('syncRecordsStartTime')) {
    // 存在,则获取剩余时间
    return (
      60 - Math.floor((Date.now() - (localStorage.getItem('syncRecordsStartTime') || 0)) / 1000)
    );
  }
  return 0; // 不存在同步剩余时间
};

// 是否执行循环方法
const isCall = () => {
  const remainTime = generateRemainTime(); // 剩余时间
  // 不存在或刚结束
  if (remainTime === 0) {
    return true;
  }
  // 存在剩余时间,并且剩余时间已经改变,下一秒
  return remainTime > 0 && store.state.timer.remainTime !== remainTime;
};

// 循环执行函数
const loop = timerId => {
  const remainTime = generateRemainTime(); // 获取剩余刷新时间
  store.dispatch('setRemainTime', remainTime); // 存储剩余时间到缓存,用于页面显示和判断
  store.dispatch('setTimerId', timerId); // 存储定时器id,到缓存,用于清除定时器
  // 结束循环,标志 remainTime = 0
  if (remainTime <= 0) {
    // eslint-disable-next-line no-use-before-define
    removeTimer(); // 清理定时器
    localStorage.removeItem('syncRecordsStartTime'); // 清除缓存刷新开始时间
  }
};

// 创建自定义定时器
export const createTimer = isActive => {
  // isActive 是否主动触发,点击按钮时,主动出发,刷新页面时,如果刷新时间未结束,是被动
  if (isActive) {
    localStorage.setItem('syncRecordsStartTime', Date.now());
  }
  setInterValCustom(loop, isCall); // 设置定时器
};

// 移除定时器
export const removeTimer = () => {
  cancleInterValCustom(store.state.timer.timerId); // 清除定时器
  store.dispatch('setRemainTime', 0); // 归零剩余时间
  store.dispatch('setTimerId', null); // 清除定时器id
};

timer.js-定时器调用
import { getRequestAnimationFrame, cancelRequestAnimationFrame } from './raf';
// 自定义定时器
export const setInterValCustom = (fn, isCall) => {
  let timer;
  const raf = getRequestAnimationFrame(); // 获取定时器方法
  const loop = () => {
    // 循环定时器
    timer = raf(loop);
    // 如果条件成立,执行回调函数
    if (isCall()) {
      fn.call(this, timer); // 调用回调方法
    }
  };
  timer = raf(loop); // 执行动画回调
};
// 关闭定时器
export const cancleInterValCustom = timer => {
  cancelRequestAnimationFrame(timer);
};

raf.js-定时器分装
function requestAnimationFramePolyfill() {
  let lastTime = 0;
  return callback => {
    const currTime = new Date().getTime();
    const timeToCall = Math.max(0, 16 - (currTime - lastTime));
    const id = window.setTimeout(() => {
      callback(currTime + timeToCall);
    }, timeToCall);
    lastTime = currTime + timeToCall;
    return id;
  };
}

export const getRequestAnimationFrame = () => {
  if (window.requestAnimationFrame) {
    return window.requestAnimationFrame;
  }
  return requestAnimationFramePolyfill();
};

export const cancelRequestAnimationFrame = id => {
  if (window.cancelAnimationFrame) {
    return window.cancelAnimationFrame(id);
  }
  return clearTimeout(id);
};

const raf = getRequestAnimationFrame();

export const cancelAnimationTimeout = frame => cancelRequestAnimationFrame(frame.id);

export const requestAnimationTimeout = (callback, delay) => {
  const start = Date.now();
  function timeout() {
    if (Date.now() - start >= delay) {
      callback();
    } else {
      // eslint-disable-next-line no-use-before-define
      frame.id = raf(timeout);
    }
  }
  const frame = {
    id: raf(timeout)
  };
  return frame;
};

moudle/timer.js-存储剩余时间和定时器id
const app = {
  state: {
    remainTime: 0,
    timerId: null
  },
  mutations: {
    REMAIN_TIME: (state, remainTime) => {
      state.remainTime = remainTime || 0;
    },
    TIMER_ID: (state, timerId) => {
      state.timerId = timerId;
    }
  },
  actions: {
    setRemainTime({ commit }, remainTime) {
      commit('REMAIN_TIME', remainTime);
    },
    setTimerId({ commit }, timerId) {
      commit('TIMER_ID', timerId);
    }
  },
  getters: {
    remainTime: state => state.remainTime || 0,
    timerId: state => state.timerId
  }
};

export default app;

visibility.js-浏览器切换后定时器停止执行
import { createTimer, removeTimer } from '@/views/statistics/uploadRecord/hooks/createTimer';

// 如果在剩余时间结束时,浏览器切换到其它tab,会导致页面仍然停留剩余时间
// 改进,在浏览器tab页面切换时,切换到其它tab,关闭定时器,切换到本tab后,启动定时器
export default {
  created() {
    window.document.addEventListener('visibilitychange', this.visibilityChange);
  },
  beforeRouteLeave() {
    window.document.removeEventListener('visibilitychange', this.visibilityChange);
  },
  methods: {
    visibilityChange() {
      // document 身上有一个属性叫作 visibilityState
      // 表示当前页面是显示或者隐藏状态
      if (document.visibilityState === 'hidden') {
        // 如果隐藏(最小化,其他网页)
        // 关闭定时器
        removeTimer();
      } else if (document.visibilityState === 'visible') {
        // 开启定时器
        createTimer(false);
      }
    }
  }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eadela

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值