日期选择器组件

1.先来看看实现效果

antdesign vue 日期选择器 getPopupContainer vue日期选择组件_Math

2.在开发之前,要先了解清楚需要完成的需求再逐个优化 (以下主要介绍思路具体实现,随机应变)

1) UI方面我们需要实现上下滚动选择,需要注意的主要细节就是滚动结束后,需要自动校正位置,比如正好滚动到两个年份之间,这时应该自动滚向占比比较大的一方

2) 第二个需要注意的就是每月的天数是不同的,1 3 5 7 8 10 12 有31天,2月闰年29天 平年28天,剩余月份30天
year % 4 === 0 && year % 100 !== 0 || year % 400 === 0为闰年)

3.实现步骤

1) 首先我们要考虑的就是这些年月日的初始化数据,我们可以看到当滚动到最下,或者最上时都是留白的所以就需要数据初始话的时候前后都添加空数据比如月份:

month: ['', '', '', 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, '', '', '']

年份初动态初始化为当前年份的上下100年,日暂时初始化为31天

2) 数据的绑定 以月为例

<div class="month" ref="month" @scroll="monthScroll">
  <li v-for="(item,index) in month" :key="index" :ref="'item'+index" class="item">
    {{ item ? item + $t('mouth') : item }}
  </li>
</div>

data () {
return {   
      date: [Date.getFullYear, 12, 1],
      month: ['', '', '', 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, '', '', ''],
      }
},
mounted () {
   this.itemHeight = this.$refs.item0[0].clientHeight
   this.initPostion()
},
monthScroll (e) { //监听月份滚动事件
    const currentTop = e.target.scrollTop
    const currentMonth = this.month[Math.round(e.target.scrollTop / this.itemHeight) + 3]
    if (this.date[1] === currentMonth) return //如果相等就赋值
    this.date.splice(1, 1, currentMonth) // 获取到当前月赋值
}

首先获取到每个item的高度,通过卷入的高度/item的高度 就可获取卷入的个数,从而获取到对应的月份值,通过this.date.splice(1, 1, currentMonth) 赋值给绑定的date数组(例:[12,2,25] 分别代表年、月、日)

3) 有了绑定的数据,我们处理,年和月的改变,从而动态计算出 每月的天数

methods:{
  handledays () {
      // 计算当前天数 并且移动
      const y = this.date[0]
      const m = this.date[1]
      const days = []
      for (let i = 0; i <= 27; i++) {
        days.push(i + 1)
      }
      if (this.isleapYears(y) && m === 2) { // 29天
        this.day = ['', '', '', ...days, 29, '', '', '']
      } else if (!this.isleapYears(y) && m === 2) { // 28天
        this.day = ['', '', '', ...days, '', '', '']
      } else if ([4, 6, 9, 11].indexOf(m) >= 0) { // 30天
        this.day = ['', '', '', ...days, 29, 30, '', '', '']
      } else { // 31天
        this.day = ['', '', '', ...days, 29, 30, 31, '', '', '']
      }
      this.$nextTick(() => {
        const currenDay = this.day[Math.round(this.$refs.day.scrollTop / this.itemHeight) + 3]
        this.date.splice(2, 1, currenDay) // 获取到当前日赋值
      })
    },	
   isleapYears (year) { // 判断平闰年
      if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
        return true // 闰年
      } else {
        return false // 平年
      }
    }
}

4) 接下来就需要解决滚动后,自动调整位置的效果

要解决这个问题,首先要解决的是如何判断当前滚动结束,还以月份为例

monthScroll (e) {
      const currentTop = e.target.scrollTop //首先记录当前出发时的位置
      setTimeout(() => {
        if (e.target.scrollTop === currentTop) { //如果300毫秒后仍相同 就认为停止滚动了
        //在此调用自动校准的方法
        this.autoFix(e, Math.round(currentTop / this.itemHeight) *this.itemHeight, currentTop)
       this.handledays(); // 动态计算出日数
        }
      }, 300)
    }

5) 接下来要处理自动校准方法的逻辑,我们需要获取到卷入为整数的高度(Math.round(currentTop / this.itemHeight) *this.itemHeight),和当前高度,然后滚动到整数位置就好了,需要主要的是滚动通过设置scrollTop的值,这不是css的属性,所以没有过渡,使用setInterval,或者js帧动画解决过渡效果

autoFix (e, top, beginTop) {
   this.auto = false // 自动调整后不再需要再次触发autoFix
      this.timer = setInterval(() => {
        if (e.target.scrollTop > top) { // 向下校正
          if (beginTop < e.target.scrollTop) window.clearInterval(this.timer) // 防止矫正位置时再次滚动导致回滚距离变大
          --e.target.scrollTop
          if (e.target.scrollTop < top) { // 防止top为小数时出现永远无法e.target.scrollTop = top导致一直运行的问题
            e.target.scrollTop += 0.5
            window.clearInterval(this.timer)
          }
        } else if (e.target.scrollTop < top) { // 向上校正
          if (beginTop > e.target.scrollTop) window.clearInterval(this.timer)
          ++e.target.scrollTop
          if (e.target.scrollTop > top) {
            e.target.scrollTop -= 0.5
            window.clearInterval(this.timer)
          }
        } else {
          window.clearInterval(this.timer)
        }
      }, 1000 / 60)
  }

6)到这里基本共功能就算实现完毕,当然还可以通过参数初始化默认值,初始默认位置,我们还是以月为例

methods:{
initPostion () {
      let [y, m, d] = this.currentDate // 为传入的初始化参数
      y = parseInt(y) || new Date().getFullYear()
      m = parseInt(m) || new Date().getMonth() + 1
      d = parseInt(d) || new Date().getDate()
      y = this.years.indexOf(y)
      m = this.month.indexOf(m)
      d = this.day.indexOf(d)
    
      if (this.$refs.day.scrollTop !== parseInt(this.itemHeight * (d - 3))) {
          this.$refs.day.scrollTop = this.itemHeight * (d - 3) // 初始化默认滚动位置
          const currenDay = this.day[Math.round(this.itemHeight * (d - 3) / this.itemHeight) + 3]
          this.date.splice(2, 1, currenDay) // 获取到当前日赋值
      }
 }
 }

开发过程中肯定会出现种种的问题 完整的程序都是一点点 调试出来的 总不会一次写到完美,比如获取开发过程中没有注意元素的clientHeight 之类的值为整数的问题 也让我踩了一脚大坑。