利用原生javascript手写datepicker组件

609 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

本文通过用原生javascript写一个datepicker组件来锻炼自己原生js能力,组件效果如下图所示: image.png

JavaScript Date 对象

生成日期对象

var d = new Date();
var d = new Date(milliseconds); // 参数为毫秒
var d = new Date(dateString);
var d = new Date(year, month, day, hours, minutes, seconds, milliseconds);

image.png

Date 对象方法

方法描述
getDate()从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay()从 Date 对象返回一周中的某一天 (0 ~ 6)。
getFullYear()从 Date 对象以四位数字返回年份。
getHours()返回 Date 对象的小时 (0 ~ 23)。
getMilliseconds()返回 Date 对象的毫秒(0 ~ 999)。
getMinutes()返回 Date 对象的分钟 (0 ~ 59)。
getMonth()从 Date 对象返回月份 (0 ~ 11)。
getSeconds()返回 Date 对象的秒数 (0 ~ 59)。
getTime()返回 1970 年 1 月 1 日至今的毫秒数。

【注意】

  • 月份需要减1,比如生成6月1的日期,那么new Date(2022, 5, 1)
  • 周日显示为0,不是7
  • 越界自动进位

当月的最后一天

new Date(2022, 6, 0) // 得到6月最后一天,即6月30号

生成日期数据

  • 首先利用自执行函数来包裹具体的内容,形成模块化,防止污染外部变量
  • 当月第一天和当月最后一天
var firstDay = new Date(year, month - 1, 1)
var lastDay = new Date(year, month, 0)
;(function () {
  var datepicker = {}

  datepicker.getMonthData = function (year, month) {
    var ret = []
    // 如果不传参数则取当前的日期
    if (!year || !month) {
      var today = new Date()
      year = today.getFullYear()
      month = today.getMonth() + 1
    }

    // 当月的第一天
    var firstDay = new Date(year, month - 1, 1)
    // 年和月有越界的问题,所以在这里重新获取一次
    year = firstDay.getFullYear()
    month = firstDay.getMonth() + 1
    // 当月的第一天是星期几
    var firstDayWeekDay = firstDay.getDay()
    if (firstDayWeekDay === 0) {
      firstDayWeekDay = 7
    }
    // 上月的最后一天
    var lastDayOfLastMonth = new Date(year, month - 1, 0)
    var lastDateOfLastMonth = lastDayOfLastMonth.getDate()

    // 需要补多少个上月的日期
    var preMonthDayCount = firstDayWeekDay - 1

    // 当月的最后一天
    var lastDay = new Date(year, month, 0)
    lastDate = lastDay.getDate()
    // 有的月份只占据4行,比如28天,有的月份占据6行,比如1号在星期日的位置,然后有31号,那么就占据了6行,所以这里显示6 * 7
    for (var i = 0; i < 7 * 6; i++) {
      var date = i + 1 - preMonthDayCount
      var showDate = date
      var thisMonth = month
      if (date <= 0) {
        // 上一个月
        thisMonth = month - 1
        showDate = lastDateOfLastMonth + date
      } else if (date > lastDate) {
        // 下一个月
        thisMonth = month + 1
        showDate = showDate - lastDate
      }
      if (thisMonth === 0) {
        // 上一年的第12月
        thisMonth = 12
      }
      if (thisMonth === 13) {
        // 下一年的第1个月
        thisMonth = 1
      }
      ret.push({
        month: thisMonth,
        date: date,
        showDate: showDate
      })
    }
    return {
      days: ret,
      year: year,
      month: month
    }
  }

  window.datepicker = datepicker
})()

js 动态渲染

拼接内容的html字符串

datepicker.buildUi = function (year, month) {
  monthData = datepicker.getMonthData(year, month)

  var html =
    '<div class="ui-datepicker-header">' +
    '<a href="#" class="ui-datepicker-btn ui-datepicker-prev-btn">&lt;</a>' +
    '<a href="#" class="ui-datepicker-btn ui-datepicker-next-btn">&gt;</a>' +
    '<span class="ui-datepicker-current-month">' +
    monthData.year +
    '-' +
    monthData.month +
    '</span>' +
    '</div>' +
    '<div class="ui-datepicker-body">' +
    '<table>' +
    '<thead>' +
    '<tr>' +
    '<th>一</th>' +
    '<th>二</th>' +
    '<th>三</th>' +
    '<th>四</th>' +
    '<th>五</th>' +
    '<th>六</th>' +
    '<th>日</th>' +
    '</tr>' +
    '</thead>' +
    '<tbody>'

  for (var i = 0; i < monthData.days.length; i++) {
    var data = monthData.days[i]
    if (i % 7 === 0) {
      // 能被7整除,说明是这一周的第一天
      html += '<tr>'
    }
    html += '<td data-date="' + data.date + '">' + data.showDate + '</td>'
    if (i % 7 === 6) {
      html += '</tr>'
    }
  }
  html += '</tbody>' + '</table>' + '</div>'
  return html
}

渲染html字符串

datepicker.render = function () {
    var html = datepicker.buildUi(year, month)
    div = document.querySelector('.ui-datepicker-wrapper')
    if (!div) {
      div = document.createElement('div')
      document.body.appendChild(div)
      div.className = 'ui-datepicker-wrapper'
    }
    div.innerHTML = html
}

datepicker组件

input框

<style>
  .datepicker {
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px;
    height: 24px;
    line-height: 24px;
    width: 230px;
  }
  .datepicker:focus {
    outline: none;
    border: 1px solid #1abc9c;
  }
</style>

<input type="text" class="datepicker" />

当点击input框的时候弹出日期组件,再次点击关闭日期组件,这里使用classList来进行显示与隐藏。

日历组件需要跟随input框的位置,所以首先把日期组件进行决定定位postion: absolute,然后在js中根据input框的位置实时的改变top left的值。

实时计算input框的位置:

var left = $input.offsetLeft
var top = $input.offsetTop
var height = $input.offsetHeight
var $input = document.querySelector(input)
    var isOpen = false
    $input.addEventListener('click', function () {
        if (isOpen) {
          div.classList.remove('ui-datepicker-wrapper-show')
          isOpen = false
        } else {
          div.classList.add('ui-datepicker-wrapper-show')
          var left = $input.offsetLeft
          var top = $input.offsetTop
          var height = $input.offsetHeight
          div.style.left = left + 'px'
          div.style.top = top + height + 2 + 'px'
          isOpen = true
        }
      },
      false
    )

点击日期进行选择

new Date(year, month, day)第三个参数可以为负数,如果是负数那么就是往上个月倒退,比如new Date(2022,5, -2),得到的日期是5月29号,也就是倒退了2天。

div.addEventListener('click', function (e) {
    var $target = e.target
    if ($target.tagName.toLowerCase() !== 'td') {
      return
    }
    var date = new Date(
      monthData.year,
      monthData.month - 1,
      $target.dataset.date
    )
    $input.value = format(date)
    div.classList.remove('ui-datepicker-wrapper-show')
    isOpen = false
  },
  false
)