原生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">&#8810;</a>' + '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-prev-btn">&lt;</a>' + '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-nextYear-btn">&#8811;</a>' + '<a href="javascript:void(0);" class="ui-datepicker-btn ui-datepicker-next-btn">&gt;</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;">&lt;!-- 引入 日期时间控件样式表文件 --&gt;&lt;link href="https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker-v1.20250113.css" rel="stylesheet" type="text/css"&gt;&lt;!-- 引入 日期时间控件 JavaScript文件 --&gt;&lt;script src="https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker-v1.20250113.js"&gt;&lt;/script&gt;支持两种格式的日期时间输入控件。短日期时间格式的:&lt;input type="text" class="datepicker"id="t1"&gt;长日期时间格式的:&lt;input type="text" class="datepicker"id="t2"&gt;&lt;script&gt; document.querySelector('#t1').value = '2008/08/20'; datepicker.init('#t1', 'yyyy/MM/dd'); datepicker.init('#t2', 'yyyy/MM/dd HH:mm:ss');&lt;/script&gt;</pre></code></body></html>最后是输入框里的小图标:
<hr>完成代码已上传到 GitHub:GitHub 源代码
【完】
页:
[1]