IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> setInterval定时器封装(settimeout |requestAnimationFrame)代码抽离,浏览器切换后定时器停止执行 -> 正文阅读

[JavaScript知识库]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);
      }
    }
  }
};

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-29 12:02:02  更:2022-04-29 12:02:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:13:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码