pda设备 发表于 2025-2-6 23:36:23

原生JS实现一个日期选择器(DatePicker)组件

这是通过原生HTML/CSS/JavaScript完成一个日期选择器(datepicker)组件,一个纯手搓的组件的开发。主要包括datepicker静态结构的编写、日历数据的计划获取、组件的渲染以及组件事件的处理。
根据调用时的时间格式参数,可以控制短日期格式或长日期格式。
实现效果(短日期格式)实现效果(长日期格式)完整代码包含4个文件:

[*]JavaScript实现功能代码全部在 datepicker-v1.20250113.js 文件
[*]CSS实现样式渲染代码全部在 datepicker-v1.20250113.css 文件
[*]HTML实现一个调用demo 在 datepicker.html 文件
[*]另外就是 输入框的小图标 calendar-icon.png
datepicker-v1.20250113.js 完整代码如下:
点击查看代码/*! * datepicker Library v1.0 * * Copyright 2025, xiongzaiqiren * Date: Mon Jan 13 2025 11:27:27 GMT+0800 (中国标准时间) */; (function () {    var datepicker = {      paramsDate: function (inputElement, targetFormat) {            this.inputElement = inputElement; // 当前输入框            this.targetFormat = targetFormat || 'yyyy/MM/dd HH:mm:ss'; // 目标日期时间格式            this.monthData = {}; // 绘制日历组件的数据源            this.sureTime = { year: 0, month: 0, date: 0, hour: -1, minute: -1, second: -1 }; // 确定的选中的日期时间,或者初始化到某个时刻,或者是初始化到当前时刻。这里时分秒必需初始化小于0,后米面才好判断是否要构建时分秒控件      },      getMonthDate: 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);//获取当月第一天            var firstDayWeekDay = firstDay.getDay();//获取星期几,才好判断排在第几列            if (0 === firstDayWeekDay) {//周日                firstDayWeekDay = 7;            }            year = firstDay.getFullYear();            month = firstDay.getMonth() + 1;            var lastDayOfLastMonth = new Date(year, month - 1, 0);//获取最后一天            var lastDateOfLastMonth = lastDayOfLastMonth.getDate();            var preMonthDayCount = firstDayWeekDay - 1;            var lastDay = new Date(year, month, 0);            var lastDate = lastDay.getDate();            for (var i = 0; i < 7 * 6; i++) {                var date = i + 1 - preMonthDayCount;                var showDate = date;                var thisMonth = month;                //上一月                if (0 >= date) {                  thisMonth = month - 1;                  showDate = lastDateOfLastMonth + date;                } else if (date > lastDate) {                  //下一月                  thisMonth = month + 1;                  showDate = showDate - lastDate;                }                if (0 === thisMonth) {                  thisMonth = 12;                }                if (13 === thisMonth) {                  thisMonth = 1;                }                ret.push({                  month: thisMonth,                  date: date,                  showDate: showDate                })            }            return {                year: year,                month: month,                days: ret            };      }    };   window.datepicker = datepicker;//该函数唯一暴露的对象    })();/***** main.js *****/(function () {   var datepicker = window.datepicker;    var $datepicker_wrapper;    //渲染函数,由于没有使用第三方插件或库,所以使用的是模板拼接的方法    datepicker.buildUi = function (monthData, sureTime) {      // monthData = datepicker.getMonthDate(year, month); // year, month, monthData 是面板需要绘画的日期时间集合      var html = '<div class="ui-datepicker-header">' +            '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-prevYear-btn">≪</a>' +            '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-prev-btn"><</a>' +            '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-nextYear-btn">≫</a>' +            '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-next-btn">></a>' +            '<span class="datepicker-curr-month">' + monthData.year + '-' + monthData.month + '</span>' +            '</div>' +            '<div class="ui-datepicker-body">' +            '<table>' +            '<thead>' +            '<tr>' +            '<th>\u4e00</th>' +            '<th>\u4e8c</th>' +            '<th>\u4e09</th>' +            '<th>\u56db</th>' +            '<th>\u4e94</th>' +            '<th>\u516d</th>' +            '<th>\u65e5</th>' +            '</tr>' +            '</thead>' +            '<tbody>';      function coreMonth(coreMonth, month) {            return coreMonth == month;      }      function isToday(year, month, date) {            const _today = new Date().getFullYear() + '/' + (new Date().getMonth() + 1) + '/' + new Date().getDate();            return (year + '/' + month + '/' + date) == _today;      }      function sureTimeIsToday(year, month, date, sureTime) {            return (!!sureTime && (sureTime.year === year && sureTime.month === month && sureTime.date === date));      }      for (var i = 0; i < monthData.days.length; i++) {            var date = monthData.days;            if (i % 7 === 0) {                html += '<tr>';            }            html += '<td class="' +                ((i % 7 === 5 || i % 7 === 6) ? 'weekend' : '') +                (coreMonth(monthData.month, date.month) ? '' : ' notmonth') +                (isToday(monthData.year, date.month, date.showDate) ? ' today' : '') +                (sureTimeIsToday(monthData.year, date.month, date.showDate, sureTime) ? ' active' : '') +                '" data-date="' + date.date + '">' + date.showDate + '</td>';            if (i % 7 === 6) {                html += '</tr>';            }      }      html += '</tbody>' +            '</table>' +            '</div>';      function buildTimeOptions(max) {            let _s = '';            for (i = 0; i <= max; i++) {                let _n = i < 10 ? ('0' + i) : i;                _s += '<option value="' + _n + '">' + _n + '</option>';            }            return _s;      }      html += '<div class="ui-datepicker-footer">' +            '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-today-btn">\u4eca\u5929</a>';      if (!!sureTime && (0 <= sureTime.hour && 0 <= sureTime.minute && 0 <= sureTime.second)) {            html += '<select class="hour">' + buildTimeOptions(23) + '</select>' +                '<select class="minute">' + buildTimeOptions(59) + '</select>' +                '<select class="second">' + buildTimeOptions(59) + '</select>';      }      html += '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-ok-btn">\u786e\u5b9a</a>' +            '</div>';      return html;    };    //日历渲染函数    datepicker.render = function (paramsDate, direction) {      var year, month;      if (!!paramsDate.monthData && 0 < paramsDate.monthData.year) {            year = paramsDate.monthData.year;            month = paramsDate.monthData.month;      }      else if (!!paramsDate.sureTime && (0 < paramsDate.sureTime.year && 0 < paramsDate.sureTime.month)) {            // 根据输入框的值初始化确定日期            year = paramsDate.sureTime.year;            month = paramsDate.sureTime.month;      }      if ('prev' === direction) {            month--;            if (month < 1) {                year--;                month = 12;            }      }      else if ('next' === direction) {            month++;      }      else if ('prevYear' === direction) {            year--;      }      else if ('nextYear' === direction) {            year++;      }      else if ('today' === direction) {            let t = new Date();            year = t.getFullYear();            month = t.getMonth() + 1;            paramsDate.sureTime.year = year;            paramsDate.sureTime.month = month;            paramsDate.sureTime.date = t.getDate();            if (0 <= paramsDate.sureTime.hour && 0 <= paramsDate.sureTime.minute && 0 <= paramsDate.sureTime.second) {                paramsDate.sureTime.hour = t.getHours();                paramsDate.sureTime.minute = t.getMinutes();                paramsDate.sureTime.second = t.getSeconds();            }      }      paramsDate.monthData = datepicker.getMonthDate(year, month); // year, month, monthData 是面板需要绘画的日期时间集合      var html = datepicker.buildUi(paramsDate.monthData, paramsDate.sureTime);      $datepicker_wrapper = document.querySelector('.ui-datepicker-wrapper');      if (!$datepicker_wrapper) {            $datepicker_wrapper = document.createElement('div');            $datepicker_wrapper.className = 'ui-datepicker-wrapper';      }      $datepicker_wrapper.innerHTML = html;      document.body.appendChild($datepicker_wrapper);      // 绘画完了,初始化选中时间      if (!!paramsDate.sureTime && (0 <= paramsDate.sureTime.hour && 0 <= paramsDate.sureTime.minute && 0 <= paramsDate.sureTime.second)) {            setSelectedByValue('.ui-datepicker-wrapper select.hour', paramsDate.sureTime.hour);            setSelectedByValue('.ui-datepicker-wrapper select.minute', paramsDate.sureTime.minute);            setSelectedByValue('.ui-datepicker-wrapper select.second', paramsDate.sureTime.second);      }    };    //初始换函数    datepicker.main = function (paramsDate) {      var $targetFormat = paramsDate.targetFormat;      var $input = paramsDate.inputElement;      // 根据输入框的值初始化控件的值      let initInputdate = new Date($input.value);      if (!!initInputdate && initInputdate.getFullYear() > 0) {            paramsDate.sureTime.year = initInputdate.getFullYear();            paramsDate.sureTime.month = initInputdate.getMonth() + 1;            paramsDate.sureTime.date = initInputdate.getDate();            if (/(?|2):():()/gi.test($input.value)) {                // 校验时分秒:格式:HH:mm:ss                paramsDate.sureTime.hour = initInputdate.getHours();                paramsDate.sureTime.minute = initInputdate.getMinutes();                paramsDate.sureTime.second = initInputdate.getSeconds();            }      }      if (0 > paramsDate.sureTime.hour || 0 > paramsDate.sureTime.minute || 0 > paramsDate.sureTime.second) {            if (!!$targetFormat && ($targetFormat.toLocaleLowerCase().includes('hh:mm:ss'))) {                // 将展示时分秒控件                paramsDate.sureTime.hour = 0;                paramsDate.sureTime.minute = 0;                paramsDate.sureTime.second = 0;            }            else {                // 不展示时分秒控件                paramsDate.sureTime.hour = -1;                paramsDate.sureTime.minute = -1;                paramsDate.sureTime.second = -1;            }      }      // 在初始化控件之前,清理掉以前的日期时间控件      const divsToRemove = document.querySelectorAll('.ui-datepicker-wrapper');      divsToRemove.forEach(div => div.remove());      datepicker.render(paramsDate);      var isOpen = false;      if (isOpen) {            $datepicker_wrapper.classList.remove('ui-datepicker-wrapper-show');            isOpen = false;      } else {            $datepicker_wrapper.classList.add('ui-datepicker-wrapper-show');            var left = $input.offsetLeft;            var top = $input.offsetTop;            var height = $input.offsetHeight;            $datepicker_wrapper.style.top = top + height + 2 + 'px';            $datepicker_wrapper.style.left = left + 'px';            isOpen = true;      }      //给按钮添加点击事件      datepicker.addEventListener($datepicker_wrapper, 'click', function (e) {            var $target = e.target;            //上一月,下一月            if ($target.classList.contains('ui-datepicker-prev-btn')) {                datepicker.render(paramsDate, 'prev');            } else if ($target.classList.contains('ui-datepicker-next-btn')) {                datepicker.render(paramsDate, 'next');            }            //上一年,下一年            else if ($target.classList.contains('ui-datepicker-prevYear-btn')) {                datepicker.render(paramsDate, 'prevYear');            } else if ($target.classList.contains('ui-datepicker-nextYear-btn')) {                datepicker.render(paramsDate, 'nextYear');            }            //今天            else if ($target.classList.contains('ui-datepicker-today-btn')) {                datepicker.render(paramsDate, 'today');            }            if ($target.tagName.toLocaleLowerCase() === 'td') {                // 移除其他日期选中状态                document.querySelectorAll('.ui-datepicker-wrapper td').forEach(function (td) {                  if (td.classList.contains('active')) {                        td.classList.remove('active');                  }                });                // 通过classname 设置选中日期                $target.classList.add('active');            }            if (!$target.classList.contains('ui-datepicker-ok-btn')) {                return true;            }            // 以下是点击“确定”之后的代码             var selected_date;            var selectedTd = document.querySelector('.ui-datepicker-wrapper td.active');            if (!!selectedTd) {                selected_date = selectedTd.dataset.date || 0;            }            if (3 === document.querySelectorAll('.ui-datepicker-wrapper select').length) {                var selectElementHour = document.querySelector('.ui-datepicker-wrapper select.hour');                paramsDate.sureTime.hour = selectElementHour.options.value || 0;                var selectElementMinute = document.querySelector('.ui-datepicker-wrapper select.minute');                paramsDate.sureTime.minute = selectElementMinute.options.value || 0;                var selectElementSecond = document.querySelector('.ui-datepicker-wrapper select.second');                paramsDate.sureTime.second = selectElementSecond.options.value || 0;            }            if (1 <= selected_date && selected_date <= 31) {                // 至少选中到天                let date;                if (0 <= paramsDate.sureTime.hour) {                  date = new Date(paramsDate.monthData.year, paramsDate.monthData.month - 1, selected_date, paramsDate.sureTime.hour, paramsDate.sureTime.minute, paramsDate.sureTime.second);                }                else {                  date = new Date(paramsDate.monthData.year, paramsDate.monthData.month - 1, selected_date);                }                $input.value = dateFormat(date, $targetFormat);            }            $datepicker_wrapper.classList.remove('ui-datepicker-wrapper-show');            isOpen = false;      }, false);    };    /* 定义一个函数,用于添加事件监听器,现代浏览器还是旧版IE浏览器。 */    datepicker.addEventListener = function (el, eventName, callback, useCapture) {      if (el.addEventListener) {            el.addEventListener(eventName, callback, useCapture);      } else if (el.attachEvent) {            el.attachEvent('on' + eventName, callback);      }    };    // 给输入框绑定点击事件    datepicker.init = function (input, targetFormat) {      this.addEventListener(document.querySelector(input), 'click', function (e) {            let $paramsDate = new datepicker.paramsDate(e.target, targetFormat);            datepicker.main($paramsDate);      });    };    // 通过value设置选中项    function setSelectedByValue(selectors, value) {      var select = document.querySelector(selectors);      if (!!!select || !!!select.options) {            return false;      }      for (var i = 0; i < select.options.length; i++) {            if (parseInt(select.options.value) == value) {                select.options.selected = true;                break;            }      }    };    /* 日期时间格式化·开始 */    Date.prototype.Format = function (fmt) {      if (!this || this.getFullYear() <= 1) return '';      var o = {            "M+": this.getMonth() + 1, //月份                   "d+": this.getDate(), //日                  "h+": this.getHours() == 0 ? 12 : this.getHours(), //小时                   "H+": this.getHours(), //小时                   "m+": this.getMinutes(), //分                   "s+": this.getSeconds(), //秒                   "q+": Math.floor((this.getMonth() + 3) / 3), //季度                   "f": this.getMilliseconds() //毫秒               };      var week = {            "0": "\u65e5",            "1": "\u4e00",            "2": "\u4e8c",            "3": "\u4e09",            "4": "\u56db",            "5": "\u4e94",            "6": "\u516d"      };      const reg_y = /(y+)/;      if (reg_y.test(fmt)) {            fmt = fmt.replace(reg_y.exec(fmt), (this.getFullYear() + "").slice(4 - reg_y.exec(fmt).length));      }      const reg_E = /(E+)/;      if (reg_E.test(fmt)) {            fmt = fmt.replace(reg_E.exec(fmt), ((reg_E.exec(fmt).length > 1) ? (reg_E.exec(fmt).length > 2 ? "\u661f\u671f" : "\u5468") : "") + week);      }      for (var k in o) {            const reg_k = new RegExp("(" + k + ")");            if (reg_k.test(fmt)) {                const t = reg_k.exec(fmt);                fmt = fmt.replace(t, (t.length == 1) ? (o) : (("00" + o).slice(("" + o).length)));            }      }      return fmt;    };    function dateFormat(date, format) {      if (!date) return '';      if (!format) format = 'yyyy/MM/dd HH:mm:ss';      if ("object" == typeof (date)) return date.Format(format);      else { return (new Date(date)).Format(format); }    };    /* 日期时间格式化·结束 */})();<hr>datepicker-v1.20250113.css 完整代码如下:
点击查看代码.datepicker {    width: 230px;        padding: 5px;        line-height: 24px;    background: url(calendar-icon.png);    background-repeat: no-repeat;    background-position: right 3px center;    padding-right: 20px;        border: 1px solid #ccc;        border-radius: 4px;    -o-border-radius: 4px;    -ms-border-radius: 4px;    -moz-border-radius: 4px;    -webkit-border-radius: 4px;    -khtml-border-radius: 4px;}.datepicker:focus {        outline: none;        border: 1px solid #1abc9c;}/* 最外层区域 */.ui-datepicker-wrapper {        display: none; /*添加默认隐藏*/    position: absolute; /*添加绝对定位*/    width: 240px;    font-size: 16px;    color: #666666;        background-color: #fff;    box-shadow: 2px 2px 8px 2px rgba(128, 128, 128, 0.3);}/* 头部区域 */.ui-datepicker-wrapper .ui-datepicker-header,.ui-datepicker-wrapper .ui-datepicker-footer {    padding: 0 20px;    height: 50px;    line-height: 50px;    text-align: center;    background: #f0f0f0;    border-bottom: 1px solid #cccccc;    font-weight: bold;}/* 设置两个按钮 */.ui-datepicker-wrapper .ui-datepicker-btn {    font-family: serif;    font-size: 20px;    width: 20px;    height: 50px;    line-height: 50px;    color: #1abc9c;    text-align: center;    cursor: pointer;    text-decoration: none;}.ui-datepicker-wrapper .ui-datepicker-prev-btn,.ui-datepicker-wrapper .ui-datepicker-prevYear-btn {    float: left;}.ui-datepicker-wrapper .ui-datepicker-next-btn,.ui-datepicker-wrapper .ui-datepicker-nextYear-btn {    float: right;}.ui-datepicker-wrapper .ui-datepicker-footer{    display: flex;    line-height: 30px;    height: 30px;    padding: 1px 1px;    background-color: #fff;}.ui-datepicker-wrapper .ui-datepicker-footer a,.ui-datepicker-wrapper select,.ui-datepicker-wrapper select option{    flex: 1 1 auto;    width: 100%;    height: 30px;    line-height: 30px;    font-size: 12px;    text-align: center;    cursor: pointer;}.ui-datepicker-wrapper .ui-datepicker-footer .ui-datepicker-btn{    height: 28px;    line-height: 28px;    border:1px solid #c0c0c0;}.ui-datepicker-wrapper .ui-datepicker-footer .ui-datepicker-btn:hover,.ui-datepicker-wrapper .ui-datepicker-footer .ui-datepicker-btn:active{    border:1px solid #1abc9c;}/* body区域 */.ui-datepicker-wrapper .ui-datepicker-body table {    width: 100%;        border-collapse: separate;    border-spacing: 1px;}/* 表头和正文 */.ui-datepicker-wrapper .ui-datepicker-body th,.ui-datepicker-wrapper .ui-datepicker-body td {    height: 30px;    text-align: center;}.ui-datepicker-wrapper .ui-datepicker-body th {    font-size: 14px;    height: 40px;    line-height: 40px;}/* 表格部分 */.ui-datepicker-wrapper .ui-datepicker-body td {    border: 1px solid #f0f0f0;    font-size: 12px;    width: 14%;    cursor: pointer;}/* “周末”的日期 */.ui-datepicker-wrapper .ui-datepicker-body td.weekend{        color: #FF5722;}/* 非“当前展示月份”的日期 */.ui-datepicker-wrapper .ui-datepicker-body td.notmonth{        background-color: #f3f3f3;}/* “今天”的日期 */.ui-datepicker-wrapper .ui-datepicker-body td.today {    border-color:#7cffe5;}.ui-datepicker-wrapper .ui-datepicker-body td.today:hover,.ui-datepicker-wrapper .ui-datepicker-body td.today:active,.ui-datepicker-wrapper .ui-datepicker-body td:hover,.ui-datepicker-wrapper .ui-datepicker-body td:focus {    border-color: #c0c0c0;}/* 选中的日期 */.ui-datepicker-wrapper .ui-datepicker-body td:active,.ui-datepicker-wrapper .ui-datepicker-body td.active {    border-color: #1abc9c;}.ui-datepicker-wrapper-show {    display: block;}<hr>datepicker.html 做的一个简易 demo 完整代码如下:
点击查看代码<!doctype html><html><head>    <meta charset="UTF-8">    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    <meta name="MobileOptimized" content="240">    <meta name="applicable-device" content="mobile">    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta name="format-detection" content="telephone=no,email=no,adress=no">    <link href="https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker-v1.20250113.css" rel="stylesheet" type="text/css">    <title>测试demo -datepicker </title>    <script src="https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker-v1.20250113.js"></script>    </head><body>    支持两种格式的日期时间输入控件。短日期时间格式的:    <input type="text" class="datepicker" id="t1">    长日期时间格式的:    <input type="text" class="datepicker" id="t2">    <!-- JS脚本 -->    <script>      // var monthDate = datepicker.getMonthDate(2019, 2);      // console.log(monthDate);      // datepicker.init(document.querySelector('.ui-datepicker-wrapper'))      document.querySelector('#t1').value = '2008/08/20';      datepicker.init('#t1', 'yyyy/MM/dd');      datepicker.init('#t2', 'yyyy/MM/dd HH:mm:ss');    </script>    调用示例:    <code><pre style="background-color: #eee;padding: 5px 3px;border: 1px solid #ccc;"><!-- 引入 日期时间控件样式表文件 --><link href="https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker-v1.20250113.css" rel="stylesheet" type="text/css"><!-- 引入 日期时间控件 JavaScript文件 --><script src="https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker-v1.20250113.js"></script>支持两种格式的日期时间输入控件。短日期时间格式的:<input type="text" class="datepicker"id="t1">长日期时间格式的:<input type="text" class="datepicker"id="t2"><script>    document.querySelector('#t1').value = '2008/08/20';    datepicker.init('#t1', 'yyyy/MM/dd');    datepicker.init('#t2', 'yyyy/MM/dd HH:mm:ss');</script></pre></code></body></html>最后是输入框里的小图标:
<hr>完成代码已上传到 GitHub:GitHub 源代码
【完】
页: [1]
查看完整版本: 原生JS实现一个日期选择器(DatePicker)组件