原生JS实现日历组件的示例代码
发布时间 - 2026-01-11 03:22:12 点击率:次想要实现的效果

- 点击日期选择框出现日历
- 有个日期控制栏帮助选择日期, 包括年、月、日的选择和今天
- 日历格子,初次点击日期选择框时显示此刻的日期,日历格子的日期应该包括这个月的所有天数,同时如果当月的1号不是周日,还应补全从周日到1号的天数。还要在这个月最后1号的后面补全到周六。
- 日期控制栏和日历格子的日期还有选择框里的日期的变化要是同步的。
实现思路
为了组件的可复用性,需要用面向对象的思想。
每个日历组件都是一个日历对象,主要包括日期选择框,日期控制显示栏,还有日历格子,为了保持日期控制显示栏和日历格子日期同步变化,日期控制栏和日历里面的每个格子都应该包含一个Date属性,点击日历里的格子,将格子存的Date属性作为函数参数,调用函数改变日期控制栏显示的时间。同理,日期控制栏时间变化时,也将Date属性作为参数调用函数,函数重新绘制日历格子。
上码:
function Calendar(parentId) {
this.parentElement = document.getElementById(parentId);
this.init();
}
Calendar.prototype = {
init: function() {
this.contains = document.createElement("div");
this.contains.onselectstart = function(){return false}; //让按钮点击时不会出现文字被选中的蓝色块
this.dateInput = document.createElement("input");
this.datePicker = document.createElement("div");
this.showDateBar = document.createElement("div");
this.dateBox = document.createElement("div");
this.icon = document.createElement("i");
this.contains.className = 'datepicker-container';
this.dateInput.className = 'date-input';
this.dateInput.readOnly = true;
var parent = this;
this.dateInput.onclick = function(event){
parent.onDateInputClick(event); //点击日期选择框时显示日历格子
};
this.contains.onblur = function(){
parent.datePicker.style.display = 'none';
}
this.datePicker.className = 'date-picker';
this.datePicker.style.display = 'none';
this.showDateBar.className = 'show-date';
this.dateBox.className = 'date-box';
this.icon.className = 'date-icon';
this.icon.innerHTML = ''; //iconfont这里用的阿里图标,可以自行替换
this.datePicker.appendChild(this.showDateBar);
this.datePicker.appendChild(this.dateBox);
this.contains.appendChild(this.dateInput);
this.contains.appendChild(this.icon);
this.contains.appendChild(this.datePicker);
this.parentElement.appendChild(this.contains);
},
}
初始化日期控制栏:
drawShowDateBar: function(parentElement){
var parent = this;
var nowDate = new Date();
parentElement.date = nowDate;
var nowYear = nowDate.getFullYear();
var nowMonth = nowDate.getMonth();
var nowDay = nowDate.getDate();
//showDateBar内容拼接
var contentStr ='<div class="year-input"><span>'+nowYear+'年</span><i class="select-year-btn"></i><ul class="year-select-box" style="display : none">';
for(var i=0;i<150;i++){
contentStr+='<li>'+(i+1900)+'年</li>';
}
contentStr+='</ul></div>'
+'<div class="month-input"><i class="prev-month"></i><select class="months-options">'
for(var i=0;i<12;i++){
contentStr+='<option>'+(i+1)+'月</option>';
}
contentStr+='</select><i class="next-month"></i></div>'
+'<div class="day-input"><i class="prev-day"></i><select class="days-options"></select>'
+'<i class="next-day"></i></div>'
+'<button class="today-btn">今天</button>'
+'<div class="days-title">';
var weekday = ['日', '一', '二', '三', '四', '五', '六'];
for (var i = 0; i < 7; i++) {
contentStr+='<span class="day-title">'+weekday[i]+'</span>';
}
contentStr+='</div>';
parentElement.innerHTML = contentStr;
this.changeShowDateBar(nowDate); //插入到showTimeBar之后,初始化,传入的参数是现在的时间
var yearInput = parentElement.firstChild;
//年选择框点击显示和隐藏选择列表
yearInput.onclick = function(){ //target和this的区别 target是触发事件的元素,this是处理事件的元素
var ul = this.lastChild;
ul.style.display==='none'||ul.style.display==='none'? ul.style.display='inline-block':ul.style.display='none';
};
//为年选择下拉框绑定点击事件
var yearSelectBox = yearInput.lastChild;
var yearLi = yearSelectBox.children;
for(var i=0;i<yearLi.length;i++){
yearLi[i].onclick = function(){
parent.showDateBar.date.setFullYear(this.innerText.slice(0,-1));
parent.changeShowDateBar(parent.showDateBar.date); //时间改变之后都要重新调用,因为不同年,不同月,某个月的天数不全一样
};
}
//为month的前后按钮添加点击事件
var monthInput = yearInput.nextSibling;
monthInput.firstChild.onclick = function(){
var monthOptions = this.nextSibling;
if(monthOptions.selectedIndex>0){
parent.showDateBar.date.setMonth(--monthOptions.selectedIndex);
}else{
monthOptions.selectedIndex = 11;
parent.showDateBar.date.setFullYear(parent.showDateBar.date.getFullYear()-1);
parent.showDateBar.date.setMonth(11);
}
parent.changeShowDateBar(parent.showDateBar.date);
};
monthInput.lastChild.onclick = function(){
var monthOptions = this.previousSibling;
if(monthOptions.selectedIndex<11){
parent.showDateBar.date.setMonth(++monthOptions.selectedIndex);
}else{
monthOptions.selectedIndex = 0;
parent.showDateBar.date.setFullYear(parent.showDateBar.date.getFullYear()+1);
parent.showDateBar.date.setMonth(0);
}
parent.changeShowDateBar(parent.showDateBar.date);
}
monthInput.children[1].onchange = function(){
parent.showDateBar.date.setMonth(this.selectedIndex);
parent.changeShowDateBar(parent.showDateBar.date)
};
//为day的前后按钮添加点击事件
var dayInput = monthInput.nextSibling;
dayInput.firstChild.onclick = function(){
var dayOptions = this.nextSibling;
if(dayOptions.selectedIndex>0){
parent.showDateBar.date.setDate(dayOptions.selectedIndex--);
}else{
parent.showDateBar.date.setMonth(parent.showDateBar.date.getMonth()-1);
parent.showDateBar.date.setDate(parent.getDaysOfMonth(parent.showDateBar.date));
}
parent.changeShowDateBar(parent.showDateBar.date);
};
dayInput.lastChild.onclick = function(){
var dayOptions = this.previousSibling;
if(dayOptions.selectedIndex < dayOptions.length-1){
dayOptions.selectedIndex++;
parent.showDateBar.date.setDate(dayOptions.selectedIndex+1);
}else{
parent.showDateBar.date.setDate(1);
parent.showDateBar.date.setMonth(parent.showDateBar.date.getMonth()+1);
}
parent.changeShowDateBar(parent.showDateBar.date);
};
dayInput.children[1].onchange = function(){
parent.showDateBar.date.setDate(this.selectedIndex+1);
parent.changeShowDateBar(parent.showDateBar.date)
};
//为今天按钮绑定点击事件
var todayBtn = dayInput.nextSibling;
todayBtn.onclick = function(){
parent.drawPicker(new Date());
parent.changeShowDateBar(new Date());
}
},
drawShowDateBar函数为日期控制栏的年份、月份、和天的点击按钮设置了点击事件处理函数。还有选择下拉框变化的处理函数。
在日期控制栏初始化时,或者改变showDateBar的Date时,都会调用changeShowDateBar 函数。这个函数主要根据传入的日期改变日期控制栏“日”下拉栏的天数,因为每个月的天数不尽相同,所以要根据传入的日期来改变。会计算出传入的日期对应的月份有多少天,使用getDaysOfMonth函数计算。
//计算一个月的天数
getDaysOfMonth: function(primalDate) {
var date = new Date(primalDate); //要新建一个对象,因为会改变date
var month = date.getMonth();
var time = date.getTime(); //计算思路主要是month+1,相减除一天的毫秒数
var newTime = date.setMonth(month + 1);
return Math.ceil((newTime - time) / (24 * 60 * 60 * 1000));
},
changeShowDateBar : function(date){
var yearInput = this.showDateBar.firstChild;
var monthInput = yearInput.nextSibling;
var dayInput = monthInput.nextSibling;
yearInput.firstChild.innerText = date.getFullYear()+'年';
var monthsOptions = monthInput.firstChild.nextSibling;
monthsOptions.selectedIndex = date.getMonth();
var daysOptions = dayInput.firstChild.nextSibling;
var days = this.getDaysOfMonth(date);
var dayStr = '';
for(var i=1;i<=days;i++){
dayStr+='<option>'+i+'日</option>';
}
daysOptions.innerHTML = dayStr;
// console.log(date.toLocaleDateString()+'changeShowDateBar');
daysOptions.selectedIndex = date.getDate()-1;
this.drawPicker(date);
},
在日期控制栏的Date变化后,日历格子的日期也应该要改变,显示的日期要和日期控制栏的保持一致。所以在changeShowDateBar函数结尾处调用drawPicker函数,重新绘制日历格子。
绘制日历格子的思路
drawPicker函数要根据传入的日期绘制日历格子。
- 首先计算传入的日期月份的天数
- 计算这个月1号是周几 。利用Date对象的date.setDate(1) //将天设置为1号 。date.getDay() //得到这天是周几
- 如果1号不是周日,则补全周日到1号的天数。可以利用oldDate.setDate(-1) //设置日期为原来日期的上个月的最后一天。注意setDate是会改变当前日期的,并不是返回新的日期。
- 从1号到这个月最后一天循环。
- 补全最后一天到周六的天数
drawPicker函数:
drawPicker: function(primalDate) {
var date = new Date(primalDate); //要新建一个对象,因为会改变date
var nowMonth = date.getMonth()+1;
var nowDate = date.getDate();
var spanContainer = [];
var dateBox = this.dateBox;
dateBox.innerHTML = '';
var time = date.getTime();
var days = this.getDaysOfMonth(date); //计算出这个月的天数
date.setDate(1); //将date的日期设置为1号
var firstDay = date.getDay(); //知道这个月1号是星期几
for (var i = 0; i < firstDay; i++) { //如果1号不是周日(一周的开头),则在1号之前要补全
var tempDate = new Date(date);
tempDate.setDate(i - firstDay + 1);
var span = document.createElement("span");
span.className = "unshow";
spanContainer.push({span : span, date : tempDate});
}
for (var i = 1; i <= days; i++) { //1号到这个月最后1天
var span = document.createElement("span");
span.className = 'show';
spanContainer.push({span : span, date : new Date(date)});
date.setDate(i + 1);
}
for (var i = date.getDay(); i <= 6; i++) { //在这个月最后一天后面补全
var span = document.createElement("span");
span.className = "unshow";
spanContainer.push({span : span, date : new Date(date)});
date.setDate(date.getDate()+1);
}
for(var i=0;i<spanContainer.length;i++){
var spanBox = spanContainer[i];
var span = spanBox.span;
span.year = spanBox.date.getFullYear(); //为每个span元素添加表示时间的属性
span.month = spanBox.date.getMonth() + 1;
span.date = spanBox.date.getDate();
span.innerText = spanBox.date.getDate();
if(span.date === nowDate&&span.month === nowMonth) //如果这个span的日期为与传入的日期匹配,设置类名为select
span.className+=" select";
var parent = this;
span.onclick = function(){ //设置点击事件
var target = event.target;
var selected = target.parentElement.getElementsByClassName("select");
for(var i=0 ;i<selected.length;i++){
selected[i].className = selected[i].className.replace(" select","");
};
target.className+=" select";
parent.changeDate(target.year, target.month, target.date);
parent.changeShowDateBar(new Date(target.year, target.month-1, target.date));
};
dateBox.appendChild(span); //将span添加到dateBox中
}
this.changeDate(primalDate.getFullYear(), primalDate.getMonth()+1, primalDate.getDate())
return;
},
//日期框点击时显示日历
onDateInputClick: function(event) {
var target = event.target;
var value = target.value;
var datePicker = this.datePicker;
if(datePicker.style.display==='none'){ //这里必须要在js文件里将datePicker.style.display设置为none,如果是在css文件里设置为none,得到的display为""
datePicker.style.display = 'block';
}else{
datePicker.style.display = 'none';
return;
}
if (!value) this.drawShowDateBar(this.showDateBar); //绘制日历的显示栏
},
changeDate : function(year, month, date){
this.dateInput.value = year+"-"+(month<10?("0"+month):month)+"-"+(date<10?("0"+date):date);
},
实现效果
有点丑......
实现中遇到的问题
- 日历格子的绘制问题 。要补全1号前面到周日的天数,还要补全当月最后1号到周六的天数。日历格子的绘制可以分为3部分,当月前面、当月和当月后面的。要计算出1号是周几,然后将这周周日到1号的天数绘制。
- 当月的日历从1号到最后1号循环绘制。补全最后1号到周六的天数(date.getDay()<=6)
- 日历格子和日期控制栏显示的同步。在绘制时为每个日历格子单元保存其代表的Date。点击格子单元时,调用changeShowDateBar函数,将单元存的Date传入,改变日期控制栏显示的日期,然后重绘日历格子。
- 每个月天数不同,出现的“日”选择框天数不同的问题。在changeShowDateBar函数里会根据传入的Date,计算当月有多少天,然后动态生成“日”选择框应有的天数。
- 跨月,跨年的处理。在日期控制栏中,有月份和日的上下按钮,在处理跨月和跨年时,判断这月(日)是否为最后一月(日),若为,则日期控制栏的Date的年(月)加1,将显示的月(日)设为第一月(日),调用changeShowDateBar函数。同理判断是否为第一月(日)。
用到的Date API
- date.getFullYear() //得到date的年份
- date.getMonth() //得到月份 0-11
- date.getDate() //得到日期 1-31的数字
- date.getDay() // 得到这天是周几 0-6
- date.getTime()// 得到date的时间戳 ms表示
- date.setFullYear(2017); // 设置年份
- date.setMonth(x) // 如果设置为0-11,则date为x年的1-12月,如果比11大,则会往前面推,会跳到x+([(n+1)/12])年的第(n+1)%12个月
- 如果为负数,例如-1则会调到上一年的最后一月去。
- date.setDate(x) // 和setMonth是同理的,它会自动根据当月的天数,判断是否发生月份的变动。-1代表date跳到上月的最后一天
- date.setTime()// 根据时间戳设置date
项目源码 https://github.com/wenkeShi/js-calendar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# JS实现日历组件
# 原生JS实现日历组件
# 原生JS日历
# 轻量级的原生js日历插件calendar.js使用指南
# JS学习之一个简易的日历控件
# js日历控件(可精确到分钟)
# php+javascript的日历控件
# 简约JS日历控件 实例代码
# 修改js Calendar日历控件 兼容IE9/谷歌/火狐
# ASP.NET中日历控件和JS版日历控件的使用方法(第5节)
# JavaScript blog式日历控件新算法
# 支持IE
# Firefox的javascript 日历控件
# 原生Js实现日历挂件
# 当月
# 周日
# 这个月
# 设置为
# 个月
# 计算出
# 在这
# 有多少
# 每个月
# 跳到
# 则会
# 绑定
# 新建一个
# 都是
# 判断是否
# 是在
# 下拉框
# 有个
# 都要
# 一个月
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
简单实现Android文件上传
Laravel如何实现密码重置功能_Laravel密码找回与重置流程
作用域操作符会触发自动加载吗_php类自动加载机制与::调用【教程】
如何续费美橙建站之星域名及服务?
专业商城网站制作公司有哪些,pi商城官网是哪个?
如何构建满足综合性能需求的优质建站方案?
微信小程序 HTTPS报错整理常见问题及解决方案
Linux系统运维自动化项目教程_Ansible批量管理实战
Laravel事件监听器怎么写_Laravel Event和Listener使用教程
Laravel怎么实现微信登录_Laravel Socialite第三方登录集成
QQ浏览器网页版登录入口 个人中心在线进入
google浏览器怎么清理缓存_谷歌浏览器清除缓存加速详细步骤
图册素材网站设计制作软件,图册的导出方式有几种?
如何在浏览器中启用Flash_2025年继续使用Flash Player的方法【过时】
制作电商网页,电商供应链怎么做?
php打包exe后无法访问网络共享_共享权限设置方法【教程】
Laravel模型事件有哪些_Laravel Model Event生命周期详解
Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区
Laravel如何与Pusher实现实时通信?(WebSocket示例)
如何彻底卸载建站之星软件?
如何用低价快速搭建高质量网站?
Laravel怎么实现观察者模式Observer_Laravel模型事件监听与解耦开发【指南】
网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?
如何在Windows 2008云服务器安全搭建网站?
深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?
高防服务器租用如何选择配置与防御等级?
Linux网络带宽限制_tc配置实践解析【教程】
今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】
Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询
JS去除重复并统计数量的实现方法
音响网站制作视频教程,隆霸音响官方网站?
佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】
Laravel如何设置定时任务(Cron Job)_Laravel调度器与任务计划配置
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
如何快速生成高效建站系统源代码?
Laravel如何处理跨站请求伪造(CSRF)保护_Laravel表单安全机制与令牌校验
详解Android——蓝牙技术 带你实现终端间数据传输
如何挑选高效建站主机与优质域名?
北京网页设计制作网站有哪些,继续教育自动播放怎么设置?
详解CentOS6.5 安装 MySQL5.1.71的方法
Laravel如何处理JSON字段_Eloquent原生JSON字段类型操作教程
Laravel Vite是做什么的_Laravel前端资源打包工具Vite配置与使用
Gemini怎么用新功能实时问答_Gemini实时问答使用【步骤】
如何在VPS电脑上快速搭建网站?
Laravel任务队列怎么用_Laravel Queues异步处理任务提升应用性能
Laravel如何配置和使用缓存?(Redis代码示例)
百度浏览器ai对话怎么关 百度浏览器ai聊天窗口隐藏
电商网站制作价格怎么算,网上拍卖流程以及规则?
Angular 表单中正确绑定输入值以确保提交与验证正常工作
b2c电商网站制作流程,b2c水平综合的电商平台?

