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

这是通过原生HTML/CSS/JavaScript完成一个日期选择器(datepicker)组件,一个纯手搓的组件的开发。主要包括datepicker静态结构的编写、日历数据的计划获取、组件的渲染以及组件事件的处理。

根据调用时的时间格式参数,可以控制短日期格式或长日期格式。

实现效果(短日期格式)实现效果(长日期格式)

完整代码包含4个文件:

  1. JavaScript实现功能代码全部在 datepicker-v1.20250113.js 文件
  2. CSS实现样式渲染代码全部在 datepicker-v1.20250113.css 文件
  3. HTML实现一个调用demo 在 datepicker.html 文件
  4. 另外就是 输入框的小图标 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[i];
 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 (/([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])/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[selectElementHour.selectedIndex].value || 0;
 var selectElementMinute = document.querySelector('.ui-datepicker-wrapper select.minute');
 paramsDate.sureTime.minute = selectElementMinute.options[selectElementMinute.selectedIndex].value || 0;
 var selectElementSecond = document.querySelector('.ui-datepicker-wrapper select.second');
 paramsDate.sureTime.second = selectElementSecond.options[selectElementSecond.selectedIndex].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[i].value) == value) {
 select.options[i].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)[1], (this.getFullYear() + "").slice(4 - reg_y.exec(fmt)[1].length));
 }
 const reg_E = /(E+)/;
 if (reg_E.test(fmt)) {
 fmt = fmt.replace(reg_E.exec(fmt)[1], ((reg_E.exec(fmt)[1].length > 1) ? (reg_E.exec(fmt)[1].length > 2 ? "\u661f\u671f" : "\u5468") : "") + week[this.getDay() + ""]);
 }
 for (var k in o) {
 const reg_k = new RegExp("(" + k + ")");
 if (reg_k.test(fmt)) {
 const t = reg_k.exec(fmt)[1];
 fmt = fmt.replace(t, (t.length == 1) ? (o[k]) : (("00" + o[k]).slice(("" + o[k]).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); }
 };
 /* 日期时间格式化·结束 */
})();

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

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="datepicker-v1.20250113.css" rel="stylesheet" type="text/css">
 <title>测试demo -datepicker </title>
 <script src="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="datepicker-v1.20250113.css" rel="stylesheet" type="text/css"&gt;
&lt;!-- 引入 日期时间控件 JavaScript文件 --&gt;
&lt;script src="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>

最后是输入框里的小图标:


完成代码已上传到 GitHub:GitHub 源代码

【完】

作者:熊仔其人原文地址:https://www.cnblogs.com/xiongzaiqiren/p/18675144/datepicker

%s 个评论

要回复文章请先登录注册