小程序组件实现倒计时功能

1,685 阅读2分钟

在开发工作中,遇到一个需求,实现一个倒计时功能,本来这个功能在网上有很多资源,直接搜索使用即可。但是,小企在开发中因为经验缘故,经历了些波折,花了一天的时间才实现该功能。特此记录下自己的经验,给后面的同学提供个台阶。

在这个功能需求的开始,觉得和网页上设计倒计时功能没啥区别,就是定时器的使用,在网上以关键词“小程序 倒计时”搜索出来很多的教程。这些教程很不错都可以使用,比如下面这个

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;
}


我在独立的页面当中测试的,该代码是完全正常的,可以使用,效果如下图

countdown1.png

但是,我的倒计时功能是在组件中使用的,把这个代码拷贝进组件的文件中,就不能正常使用了。经过断点调试,他的定时间就失效了,执行第一次是正常的,定时器执行第二次的时候,所有使用到的变量就变为未定义了。由此开始我自己的修改折腾之路。

在折腾中遇到以下几个坑

第一:在小程序自定义组件中,方法函数必须写在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');
            },
      },

})

修改后的效果:

1659666015130.png