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


Posted in Javascript onSeptember 22, 2017

想要实现的效果

  • 点击日期选择框出现日历
  • 有个日期控制栏帮助选择日期, 包括年、月、日的选择和今天
  • 日历格子,初次点击日期选择框时显示此刻的日期,日历格子的日期应该包括这个月的所有天数,同时如果当月的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);
  },

实现效果

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

有点丑......

实现中遇到的问题

  • 日历格子的绘制问题 。要补全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

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

Javascript 相关文章推荐
Chrome中模态对话框showModalDialog返回值问题的解决方法
May 25 Javascript
写js时遇到的一些小问题
Dec 06 Javascript
读jQuery之十四 (触发事件核心方法)
Aug 23 Javascript
js继承call()和apply()方法总结
Dec 08 Javascript
jQuery soColorPacker 网页拾色器
Jun 22 Javascript
JSON与String互转的实现方法(Javascript)
Sep 27 Javascript
vue构建单页面应用实战
Apr 10 Javascript
webpack开发跨域问题解决办法
Aug 03 Javascript
vue mint-ui 实现省市区街道4级联动示例(仿淘宝京东收货地址4级联动)
Oct 16 Javascript
JavaScript基于遍历操作实现对象深拷贝功能示例
Mar 05 Javascript
vue组件间的参数传递实例详解
Apr 26 Javascript
浅谈react useEffect闭包的坑
Jun 08 Javascript
BootstrapTable加载按钮功能实例代码详解
Sep 22 #Javascript
Bootstrap Table 删除和批量删除
Sep 22 #Javascript
jQury Ajax使用Token验证身份实例代码
Sep 22 #Javascript
Angular将填入表单的数据渲染到表格的方法
Sep 22 #Javascript
详解在express站点中使用ejs模板引擎
Sep 21 #Javascript
vue router学习之动态路由和嵌套路由详解
Sep 21 #Javascript
vue-router 权限控制的示例代码
Sep 21 #Javascript
You might like
德生PL330的评价与改造
2021/03/02 无线电
提升PHP执行速度全攻略(上)
2006/10/09 PHP
php使用GeoIP库实例
2014/06/27 PHP
用JQuery 实现的自定义对话框
2007/03/24 Javascript
jquery 元素相对定位代码
2010/10/15 Javascript
JavaScript的漂亮的代码片段
2013/06/05 Javascript
javascript将DOM节点添加到文档的方法实例分析
2015/08/04 Javascript
BootStrap Table 分页后重新搜索问题的解决办法
2016/08/08 Javascript
详解js中call与apply关键字的作用
2016/11/21 Javascript
js实现贪吃蛇小游戏(容易理解)
2017/01/22 Javascript
jQuery动态生成不规则表格(前后端)
2017/02/21 Javascript
JS自定义滚动条效果简单实现代码
2020/10/27 Javascript
AngularJS 中的数据源的循环输出
2017/10/12 Javascript
基于Web Audio API实现音频可视化效果
2020/06/12 Javascript
[02:04]完美世界城市挑战赛秋季赛报名开始 谁是solo路人王?
2019/10/10 DOTA
[01:33]PWL开团时刻DAY2-开雾与反开雾
2020/10/31 DOTA
python在linux系统下获取系统内存使用情况的方法
2015/05/11 Python
python编码总结(编码类型、格式、转码)
2016/07/01 Python
Python实现备份MySQL数据库的方法示例
2018/01/11 Python
浅谈Python中的私有变量
2018/02/28 Python
Python 查找字符在字符串中的位置实例
2018/05/02 Python
python类的实例化问题解决
2019/08/31 Python
使用python实现离散时间傅里叶变换的方法
2019/09/02 Python
python中@property和property函数常见使用方法示例
2019/10/21 Python
python matplotlib中的subplot函数使用详解
2020/01/19 Python
python将音频进行变速的操作方法
2020/04/08 Python
Python DES加密实现原理及实例解析
2020/07/17 Python
python和go语言的区别是什么
2020/07/20 Python
美国第一香水网站:Perfume.com
2017/01/23 全球购物
ECCO爱步加拿大官网:北欧丹麦鞋履及皮具品牌
2017/07/08 全球购物
党章学习思想汇报
2014/01/14 职场文书
认错检讨书
2014/10/02 职场文书
2014年高中教师工作总结
2014/12/19 职场文书
三八妇女节寄语
2015/02/27 职场文书
煤矿安全学习心得体会
2016/01/18 职场文书
大学生饮品店创业计划书范文
2019/07/10 职场文书