热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

原生JS实现日历组件的示例代码

想要实现的效果 点击日期选择框出现日历 有个日期控制栏帮助选择日期, 包括年、月、日的选择和今天 日历格子,初次点击日

想要实现的效果

  • 点击日期选择框出现日历
  • 有个日期控制栏帮助选择日期, 包括年、月、日的选择和今天
  • 日历格子,初次点击日期选择框时显示此刻的日期,日历格子的日期应该包括这个月的所有天数,同时如果当月的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='
'+nowYear+'年
    '; for(var i=0;i<150;i++){ contentStr+='
  • '+(i+1900)+'年
  • '; } contentStr+='
' +'
' +'
' +'
' +'' +'
'; var weekday = ['日', '一', '二', '三', '四', '五', '六']; for (var i = 0; i <7; i++) { contentStr+=''+weekday[i]+''; } contentStr+='
'; 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'&#63; ul.style.display='inline-block':ul.style.display='none'; }; //为年选择下拉框绑定点击事件 var yearSelectBox = yearInput.lastChild; var yearLi = yearSelectBox.children; for(var i=0;i0){ 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

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+='';
    }
    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 
//日期框点击时显示日历
  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&#63;("0"+month):month)+"-"+(date<10&#63;("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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • PTArchiver工作原理详解与应用分析
    PTArchiver工作原理及其应用分析本文详细解析了PTArchiver的工作机制,探讨了其在数据归档和管理中的应用。PTArchiver通过高效的压缩算法和灵活的存储策略,实现了对大规模数据的高效管理和长期保存。文章还介绍了其在企业级数据备份、历史数据迁移等场景中的实际应用案例,为用户提供了实用的操作建议和技术支持。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • Webdriver中元素定位的多种技术与策略
    在Webdriver中,元素定位是自动化测试的关键环节。本文详细介绍了8种常用的元素定位技术与策略,包括ID、名称、标签名、类名、链接文本、部分链接文本、XPath和CSS选择器。每种方法都有其独特的优势和适用场景,通过合理选择和组合使用,可以显著提高测试脚本的稳定性和效率。此外,文章还探讨了在复杂页面结构中如何灵活运用这些定位技术,以应对各种挑战。 ... [详细]
  • Git命令基础应用指南
    本指南详细介绍了Git命令的基础应用,包括如何使用`git clone`从远程服务器克隆仓库(例如:`git clone [url/path/repository]`)以及如何克隆本地仓库(例如:`git clone [local/path/repository]`)。此外,还提供了常见的Git操作技巧,帮助开发者高效管理代码版本。 ... [详细]
  • 在Conda环境中高效配置并安装PyTorch和TensorFlow GPU版的方法如下:首先,创建一个新的Conda环境以避免与基础环境发生冲突,例如使用 `conda create -n pytorch_gpu python=3.7` 命令。接着,激活该环境,确保所有依赖项都正确安装。此外,建议在安装过程中指定CUDA版本,以确保与GPU兼容性。通过这些步骤,可以确保PyTorch和TensorFlow GPU版的顺利安装和运行。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 在使用Eclipse进行调试时,如果遇到未解析的断点(unresolved breakpoint)并显示“未加载符号表,请使用‘file’命令加载目标文件以进行调试”的错误提示,这通常是因为调试器未能正确加载符号表。解决此问题的方法是通过GDB的`file`命令手动加载目标文件,以便调试器能够识别和解析断点。具体操作为在GDB命令行中输入 `(gdb) file `。这一步骤确保了调试环境能够正确访问和解析程序中的符号信息,从而实现有效的调试。 ... [详细]
  • 本文详细解析了逻辑运算符“与”(&&)和“或”(||)在编程中的应用。通过具体示例,如 `[dehua@teacher~]$[$(id -u) -eq 0] && echo "You are root" || echo "You must be root"`,展示了如何利用这些运算符进行条件判断和命令执行。此外,文章还探讨了这些运算符在不同编程语言中的实现和最佳实践,帮助读者更好地理解和运用逻辑运算符。 ... [详细]
  • 二分查找算法详解与应用分析:本文深入探讨了二分查找算法的实现细节及其在实际问题中的应用。通过定义 `binary_search` 函数,详细介绍了算法的逻辑流程,包括初始化上下界、循环条件以及中间值的计算方法。此外,还讨论了该算法的时间复杂度和空间复杂度,并提供了多个应用场景示例,帮助读者更好地理解和掌握这一高效查找技术。 ... [详细]
  • 在 Android 开发中,`android:exported` 属性用于控制组件(如 Activity、Service、BroadcastReceiver 和 ContentProvider)是否可以被其他应用组件访问或与其交互。若将此属性设为 `true`,则允许外部应用调用或与之交互;反之,若设为 `false`,则仅限于同一应用内的组件进行访问。这一属性对于确保应用的安全性和隐私保护至关重要。 ... [详细]
  • ClassList对象学习心得与表单事件非空校验技巧
    ClassList对象学习心得与表单事件非空校验技巧 ... [详细]
  • 蚂蚁课堂:性能测试工具深度解析——JMeter应用与实践
    蚂蚁课堂:性能测试工具深度解析——JMeter应用与实践 ... [详细]
  • 在最近的项目中,我们广泛使用了Qt框架的网络库,过程中遇到了一些挑战和问题。本文旨在记录这些经验和解决方案,以便日后参考。鉴于我们的客户端GUI完全基于Qt开发,我们期望利用其强大的网络功能进行Fiddler网络数据包的捕获与分析,以提升开发效率和应用性能。 ... [详细]
  • 在 iOS 开发中,经常会遇到 `@(YES)`、`@[firstViewController]` 以及 `@{@a:@b}` 这样的语法糖。这些简化的写法分别用于初始化布尔值、数组和字典对象,能够显著提高代码的可读性和编写效率。例如,`@(YES)` 可以快速创建一个布尔值对象,`@[firstViewController]` 则用于创建包含单个元素的数组,而 `@{@a:@b}` 则用于创建键值对字典。理解这些语法糖的使用方法,有助于开发者更加高效地进行编码。 ... [详细]
author-avatar
天地菲人间_984
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有