原生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 相关文章推荐
js Dialog 实践分享
Oct 22 Javascript
浅谈window对象的scrollBy()方法
Jul 15 Javascript
js基于面向对象实现网页TAB选项卡菜单效果代码
Sep 09 Javascript
javascript实现Email邮件显示与删除功能
Nov 21 Javascript
探索Javascript中this的奥秘
Dec 11 Javascript
利用js判断手机是否安装某个app的多种方案
Feb 13 Javascript
DOM事件探秘篇
Feb 15 Javascript
Node做中转服务器转发接口
Oct 18 Javascript
Vue 应用中结合vux使用微信 jssdk的方法
Aug 28 Javascript
使用zrender.js绘制体温单效果
Oct 31 Javascript
vue动态渲染svg、添加点击事件的实现
Mar 13 Javascript
vue通过接口直接下载java生成好的Excel表格案例
Oct 26 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
使用PHPMYADMIN操作mysql数据库添加新用户和数据库的方法
2010/04/02 PHP
WordPress中缩略图的使用以及相关技巧
2015/11/24 PHP
php实现微信公众平台发红包功能
2018/06/14 PHP
laravel通过a标签从视图向控制器实现传值
2019/10/15 PHP
JQGrid的用法解析(列编辑,添加行,删除行)
2013/11/08 Javascript
js实现随机抽选效果、随机抽选红色球效果
2017/01/13 Javascript
解决URL地址中的中文乱码问题的办法
2017/02/10 Javascript
基于jquery日历价格、库存等设置插件
2020/07/05 jQuery
详解关于react-redux中的connect用法介绍及原理解析
2017/09/11 Javascript
微信小程序简单实现form表单获取输入数据功能示例
2017/11/30 Javascript
JS实现的简单分页功能示例
2018/08/23 Javascript
用POSTMAN发送JSON格式的POST请求示例
2018/09/04 Javascript
angularJS1 url中携带参数的获取方法
2018/10/09 Javascript
Jquery获取radio选中值实例总结
2019/01/17 jQuery
Nodejs实现用户注册功能
2019/04/14 NodeJs
vue实现匀速轮播效果
2020/06/29 Javascript
用Python进行TCP网络编程的教程
2015/04/29 Python
Python写入数据到MP3文件中的方法
2015/07/10 Python
Python自然语言处理之词干,词形与最大匹配算法代码详解
2017/11/16 Python
Python图形绘制操作之正弦曲线实现方法分析
2017/12/25 Python
Python面向对象编程之继承与多态详解
2018/01/16 Python
Python聊天室程序(基础版)
2018/04/01 Python
浅析python3字符串格式化format()函数的简单用法
2018/12/07 Python
Python实现的爬取百度文库功能示例
2019/02/16 Python
详解pandas库pd.read_excel操作读取excel文件参数整理与实例
2019/02/17 Python
Python3列表内置方法大全及示例代码小结
2019/05/10 Python
python实现布隆过滤器及原理解析
2019/12/08 Python
Python如何把十进制数转换成ip地址
2020/05/25 Python
仓库门卫岗位职责
2013/12/22 职场文书
优秀女职工事迹材料
2014/02/06 职场文书
霸王洗发水广告词
2014/03/14 职场文书
《欢乐的泼水节》教学反思
2014/04/22 职场文书
留学推荐信怎么写
2015/03/26 职场文书
2015年高校教师个人工作总结
2015/05/25 职场文书
四十年同学聚会致辞
2015/07/28 职场文书
导游词之江苏溱潼古镇
2019/11/27 职场文书