在开发工作中,遇到一个需求,实现一个倒计时功能,本来这个功能在网上有很多资源,直接搜索使用即可。但是,小企在开发中因为经验缘故,经历了些波折,花了一天的时间才实现该功能。特此记录下自己的经验,给后面的同学提供个台阶。
在这个功能需求的开始,觉得和网页上设计倒计时功能没啥区别,就是定时器的使用,在网上以关键词“小程序 倒计时”搜索出来很多的教程。这些教程很不错都可以使用,比如下面这个
wxml代码
<view class="title-item">倒计时</view>
<view class="countdown-item">
<view class="countdown-title">
<block>
<text class='tui-conutdown-box'>{{countdowntime}}</text>
</block>
</view>
</view>
JS代码
Page({
data: {
nowDate: '2022-12-22 18:00:00', //结束时间
countdowntime: '', //倒计时
},
countTime() {
let days, hours, minutes, seconds;
let nowDate = this.data.nowDate; //获取开始 结束时间
console.log('nowDate', nowDate)
let that = this;
let now = new Date().getTime();
let end = new Date(nowDate).getTime(); //设置截止时间
// console.log("开始时间:" + now, "截止时间:" + end);
let leftTime = end - now; //时间差
if (leftTime >= 0) {
days = Math.floor(leftTime / 1000 / 60 / 60 / 24);
hours = Math.floor(leftTime / 1000 / 60 / 60 % 24);
minutes = Math.floor(leftTime / 1000 / 60 % 60);
seconds = Math.floor(leftTime / 1000 % 60);
seconds = seconds < 10 ? "0" + seconds : seconds
minutes = minutes < 10 ? "0" + minutes : minutes
hours = hours < 10 ? "0" + hours : hours
that.setData({
countdowntime: days + " : " + hours + " :" + minutes + " :" + seconds,
})
//递归每秒调用countTime方法,显示动态时间效果
setTimeout(that.countTime, 1000);
} else {
that.setData({
countdowntime: '未开始'
})
}
},
onLoad: function (options) {
this.countTime();
},
})
wxss代码
.countdown-item {
width: 100%;
height: 100rpx;
border: 0rpx solid red;
}
.countdown-title {
width: 100%;
height: 50rpx;
line-height: 50rpx;
font-size: 40rpx;
color: #fff;
}
.tui-conutdown-box {
display: inline-block;
line-height: 50rpx;
text-align: center;
background-color: red;
color: #fff;
margin: 0 4rpx;
padding: 10rpx 20rpx;
}
.tui-countdown-bg {
background-color: #DF0101;
}
.countdown-text{
color: #000;
}
我在独立的页面当中测试的,该代码是完全正常的,可以使用,效果如下图
但是,我的倒计时功能是在组件中使用的,把这个代码拷贝进组件的文件中,就不能正常使用了。经过断点调试,他的定时间就失效了,执行第一次是正常的,定时器执行第二次的时候,所有使用到的变量就变为未定义了。由此开始我自己的修改折腾之路。
在折腾中遇到以下几个坑
第一:在小程序自定义组件中,方法函数必须写在methods{}内部,否则无法调用 第二:组件中properties传递的值只在组件被使用的初始时有值(这里也许不对,我这样认知是因为我在组件定时器中第二次以后获取不到值)
以下是在组件中使用上面代码的错误示例,只粘贴JS部分
const App = getApp();
Component({
options: {
addGlobalClass: true,
},
/**
* 组件的属性列表
* 用于组件自定义设置
*/
properties: {
itemIndex: String,
itemStyle: Object,
params: Object,
dataList: Object,
day: 11,
},
data: {
selectedIndex: 0,
countdowntime: 0, //倒计时
jiezhitime: ''
},
// 组件生命周期函数 - 在组件实例进入页面节点树时执行)
lifetimes: {
attached: function () {
this.setData({
jiezhitime: this.properties.dataList[0].time
})
console.log('12354');
this.countTime()
},
},
/**
* 组件的方法列表
* 更新属性和数据的方法与更新页面数据的方法类似
*/
methods: {
countTime: function () {
let days, hours, minutes, seconds;
let nowDate = this.data.jiezhitime; //获取开始 结束时间
console.log({ nowDate })
let that = this;
let now = new Date().getTime();
let end = new Date(nowDate).getTime(); //设置截止时间
// console.log("开始时间:" + now, "截止时间:" + end);
let leftTime = end - now; //时间差
if (leftTime >= 0) {
days = Math.floor(leftTime / 1000 / 60 / 60 / 24);
hours = Math.floor(leftTime / 1000 / 60 / 60 % 24);
minutes = Math.floor(leftTime / 1000 / 60 % 60);
seconds = Math.floor(leftTime / 1000 % 60);
seconds = seconds < 10 ? "0" + seconds : seconds
minutes = minutes < 10 ? "0" + minutes : minutes
hours = hours < 10 ? "0" + hours : hours
console.log({
that
});
that.setData({
countdowntime: days + " : " + hours + " :" + minutes + " :" + seconds,
})
console.log(that.data.countdowntime)
//递归每秒调用countTime方法,显示动态时间效果
// setTimeout(that.countTime, 1000); // Cannot read property 'jiezhitime' of undefined at Function.countTime (index.js? [sm]:37) 这里递归调用会一直出错
} else {
// that.setData({
// countdowntime: '未开始'
// })
}
},
},
})
经过调试,确定是定时器递归调用产生的问题,也就是说递归调用的时候,获取组件页面的data中值不存在。那就要想办法修改定时器的使用方法,让他能够实现倒计时的动态效果。于是继续在网上查找资料,有一位大佬的资料写的很详细,我把他的方法进行了修改,实现了自己想要的功能。这里粘贴下大佬的链接,有需求的可以直接去看blog.51cto.com/u_15499114/…
以下是我修改后的实现倒计时功能的代码
wxml代码
<view wx:if="{{dataList}}">距开始时间
<text wx:if="{{d}}" class='time-number'>{{d}}</text>
<span wx:if="{{d}}" class='time-text'>:</span>
<text class='time-number'>{{h}}</text>
<span class='time-text'>:</span>
<text class='time-number'>{{m}}</text>
<span class='time-text '>:</span>
<text class='time-number '>{{s}}</text>
</view>
<view wx:else>
距开始时间: <text class='time-number'>未开始</text>
</view>
JS代码:
const App = getApp();
var timer = 0;
var interval = 1000;
Component({
options: {
addGlobalClass: true,
},
/**
* 组件的属性列表
* 用于组件自定义设置
*/
properties: {
itemIndex: String,
itemStyle: Object,
params: Object,
dataList: Object,
day: 11,
},
data: {
selectedIndex: 0,
d: 0, //天
h: 0, //时
m: 0, //分
s: 0, //秒
result: '', //自定义格式返回页面显示结果
lastTime: '' //倒计时的时间错
},
// 组件生命周期函数 - 在组件实例进入页面节点树时执行)
lifetimes: {
attached() {
console.log('123123',this.properties)
//组件创建时
this.setData({
lastTime: this.initTime(this.properties).lastTime, //根据 target 初始化组件的lastTime属性
}, () => {
//开启定时器
this.tick();
//判断是否有format属性 如果设置按照自定义format处理页面上显示的时间 没有设置按照默认的格式处理
this.defaultFormat(this.data.lastTime)
})
},
detached() {
//组件销毁时清除定时器 防止爆栈
clearTimeout(timer);
},
},
/**
* 组件的方法列表
* 更新属性和数据的方法与更新页面数据的方法类似
*/
methods: {
//默认处理时间格式
defaultFormat: function (time) {
const day = 24 * 60 * 60 * 1000
const hours = 60 * 60 * 1000;
const minutes = 60 * 1000;
const d = Math.floor(time / day);
const h = Math.floor((time - d * day) / hours);
const m = Math.floor((time - d * day - h * hours) / minutes);
const s = Math.floor((time - d * day - h * hours - m * minutes) / 1000);
this.setData({
d,
h: h< 10 ? '0' + h : h ,
m: m < 10 ? '0'+ m : m,
s: s < 10 ? '0'+ s : s
})
},
//定时事件
tick: function () {
let {
lastTime
} = this.data;
timer = setTimeout(() => {
if (lastTime < interval) {
clearTimeout(timer);
this.setData({
lastTime: 0,
result: ''
},
() => {
this.defaultFormat(lastTime)
if (this.onEnd) {
this.onEnd();
}
}
);
} else {
lastTime -= interval;
this.setData({
lastTime, //输出时间
result: lastTime
},
() => {
this.defaultFormat(lastTime)
this.tick();
}
);
}
}, interval);
},
//初始化时间
initTime: function (properties) {
let lastTime = 0;
let targetTime = 0;
let endtime = new Date().getTime();
if(properties.dataList != null){
endtime = properties.dataList[0].time
}
targetTime = new Date(endtime).getTime();
lastTime = targetTime - ( new Date().getTime() ) ;
return {
lastTime: lastTime < 0 ? 0 : lastTime,
};
},
//时间结束回调事件
onEnd: function () {
this.triggerEvent('onEnd');
},
},
})
修改后的效果: