这是通过原生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[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:ssparamsDate.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;">
<!-- 引入 日期时间控件样式表文件 -->
<link href="datepicker-v1.20250113.css" rel="stylesheet" type="text/css">
<!-- 引入 日期时间控件 JavaScript文件 -->
<script src="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>
最后是输入框里的小图标:
完成代码已上传到 GitHub:GitHub 源代码
【完】