社区编辑申请
注册/登录
HarmonyOS - 自定义组件之计时器
系统 OpenHarmony
前段时间项目中遇到了计时器的功能,项目中的计时器其实只是显示功能,数据全是由设备上报的。完成项目后,自己做了一个小的计时器组件,在这个过程中也发现了一些问题。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

前言

前段时间项目中遇到了计时器的功能,项目中的计时器其实只是显示功能,数据全是由设备上报的。完成项目后,自己做了一个小的计时器组件,在这个过程中也发现了一些问题。

效果展示

组件直接传入以秒为单位的数据,最终显示如下效果:

#夏日挑战赛# HarmonyOS - 自定义组件之计时器-开源基础软件社区

实现原理

1、用setTimeout模拟setInterval的行为

正常情况下,说到计时器首先想到的是使用setInterval,对比setTimeout要去重复调用,setInterval很方便就能实现,如下代码:

getTime(time) {
this.countNum = time;
setTimeout(() => {
this.getTime(time --)
}, 1000)
},
setInterval(() => {
this.countNum --
}, 1000)

但为什么要使用setTimeout呢,查下两个定时器的原理,会发现,创建一个时间间隔为100ms的定时器,setInterval每隔100ms往队列中添加一个事件;100ms后,添加T1定时器代码至队列中,主线程中还有任务在执行,所以等待,some event执行结束后执行T1定时器代码;又过了100ms,T2定时器被添加到队列中,主线程还在执行T1代码,所以等待;又过了100ms,理论上又要往队列里推一个定时器代码,但由于此时T2还在队列中,所以T3不会被添加,结果就是此时被跳过;这里我们可以看到,T1定时器执行结束后马上执行了T2代码,所以并没有达到定时器的效果。

#夏日挑战赛# HarmonyOS - 自定义组件之计时器-开源基础软件社区

综上所述,setInterval有两个缺点:

  1. 使用setInterval时,某些间隔会被跳过。
  2. 可能多个定时器会连续执行。

所以,我们要使用setTimeout模拟setInterval,来规避掉上面的缺点。

2、用Date.now()获取当前时间,规避浏览器退出再进来造成的计时误差

当我们在使用计时工具的时候,因为一些原因,将浏览器退到后台,再次进来的时候,发现计时器时间不对,感觉刚才退出去的这段时间,计时器是停止状态。针对这个问题,查询会发现,出于节能的考虑, 部分浏览器在进入后台时(或者失去焦点时), 会将 setTimeout 等定时任务暂停,待用户回到浏览器时, 才会重新激活定时任务。

说是暂停,实践操作会发现其实应该说是延迟, 1s 的任务延迟到 2s, 或者更久,总之,计时器计算掉的时间,比实际过去的时间少。解决这个问题,我们可以使用Date.now()记录时间,如下,计算两次计时事件的时间差,来计算每次计时的step。

getTime(time) {
this.countNum = time;
setTimeout(() => {
const nowDate = Date.now()
const diff = Math.floor((nowDate - this.curTime) / 1000)
const step = diff > 1 ? diff : 1 // 页面退到后台后计时有偏差,对比时间差,得到计时step
this.curTime = nowDate
this.getTime(time - step)
}, 1000)
},

3、及时清理定时器

这是容易忽略的一点,我们的组件唯一的一个prop属性就是time,实际的业务场景中,可能会有一些操作改变倒计时的时长,所以我们的组件需要监听time值的改变,来做一些初始化操作,这时候你会发现,当做了2次初始化操作后,我们的代码中会同时存在了2个计时器,时间过了1秒后2个计时器同时触发,对time值做了2次“- 1”操作,所以,我们在初始化时要清除之前的定时器。

getTime(time) {
this.timer && clearTimeout(this.timer) //清除定时器
this.countNum = time;
this.timer = setTimeout(() => {
const nowDate = Date.now()
const diff = Math.floor((nowDate - this.curTime) / 1000)
const step = diff > 1 ? diff : 1 // 页面退到后台后计时有偏差,对比时间差,得到计时step
this.curTime = nowDate
this.getTime(time - step)
}, 1000)
},

实现过程

countDown组件hml部分:

<div class="count-down">
<image class="count" src="./count.png"></image>
<div class="countNumCon">
<text class="countNum">
{{countNum}}
</text>
</div>
</div>

countDown组件js部分:

export default {
props: [
'time'
],
data: {
timer: null, //定时器
curTime: 0, //记录上次操作时间
countNum: '',
},
onInit() {
this.countDown()
this.$watch('time', 'countDown');
},
//将传入的时间(s)转换成天/小时/分钟/秒
durationFormatter(time) {
if (!time) return { ss: 0 }
let t = time
const ss = t % 60
t = (t - ss) / 60
if (t < 1) return { ss }
const mm = t % 60
t = (t - mm) / 60
if (t < 1) return { mm, ss }
const hh = t % 24
t = (t - hh) / 24
if (t < 1) return { hh, mm, ss }
const dd = t
return { dd, hh, mm, ss }
},
//处理时间格式
dealTime(time){
return `00${time || ''}`.slice(-2);
},
countDown() {
this.curTime = Date.now()
this.getTime(this.time)
},
//计算时间
getTime(time) {
this.timer && clearTimeout(this.timer)
if (time < 0) {
return
}
const { dd, hh, mm, ss } = this.durationFormatter(time)
this.countNum = `${dd || 0}天 ${this.dealTime(hh)}:${this.dealTime(mm)}:${this.dealTime(ss)}`;
this.timer = setTimeout(() => {
const nowDate = Date.now()
const diff = Math.floor((nowDate - this.curTime) / 1000)
const step = diff > 1 ? diff : 1 // 页面退到后台的时候不会计时,对比时间差,大于1s的重置倒计时
this.curTime = nowDate
this.getTime(time - step)
}, 1000)
},
};

countDown组件css部分:

.count-down {
display: flex;
margin-top: 200px;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 50%;
}
.countImg {
width: 200px;
height: 100px;
}
.numContainer {
background-color: black;
height: 57px;
width: 140px;
left: 8px;
top: -80px;
justify-content: center;
border-radius: 7px;
}
.count {
color: white;
font-size: 22px;
font-weight: 500;
}

父组件hml部分:

<element name="countDown" src="../countDown/countDown.hml">
</element>
<div class="container">
<countDown time="{{ leftTime }}">
</countDown>
</div>

父组件js部分:

export default {
data: {
leftTime: 100,
},
};

总结

这个计时器组件就是对日常工作的发散,作为一个鸿蒙小白,顺便做一个小小的练习。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。

责任编辑:jianghua 来源: 鸿蒙社区

同话题下的热门内容

HarmonyOS - HDC命令与ADB命令使用对比OHOS构建自定义服务实战啃论文俱乐部—数据密集型应用内存压缩HarmonyOS - 自定义组件之计时器HarmonyOS - 方舟开发框架ArkUI 流光按钮效果基于OpenHarmony3.1的购物车应用的实现OpenHarmony3.1-Ace-Formcomponent源码解析OpenHarmony HiSysEvent打点调用实践(L2)

编辑推荐

HarmonyOS 2.0鸿蒙第二期开发者Beta公测申请指南HarmonyOS LYEVK-3861开发板播放《蜜雪冰城》鸿蒙HarmonyOS分布式软总线:构建低时延、高带宽的多设备虚拟网络华为HarmonyOS的强势突围: 直面物联网迷宫的蓄力进击鸿蒙HarmonyOS2.0发布会现场回忆录
我收藏的内容
点赞
收藏

51CTO技术栈公众号