原生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 相关文章推荐
Javascript变量函数浅析
Sep 02 Javascript
javascript动态添加样式(行内式/嵌入式/外链式等规则)
Jun 24 Javascript
Javascript 鼠标移动上去 滑块跟随效果代码分享
Nov 23 Javascript
三种检测iPhone/iPad设备方向的方法
Apr 23 Javascript
JQuery判断radio是否选中并获取选中值的示例代码
Oct 17 Javascript
15个值得开发人员关注的jQuery开发技巧和心得总结【经典收藏】
May 25 Javascript
深入解析JavaScript中的arguments对象
Jun 12 Javascript
js removeChild 方法深入理解
Aug 16 Javascript
JS实现京东首页之页面顶部、Logo和搜索框功能
Jan 12 Javascript
angular6的响应式表单的实现
Oct 10 Javascript
JavaScript随机数的组合问题案例分析
May 16 Javascript
vue cli 3.0通用打包配置代码,不分一二级目录
Sep 02 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
php实现粘贴截图并完成上传功能
2015/05/17 PHP
Session 失效的原因汇总及解决丢失办法
2015/09/30 PHP
PHP爬虫之百万级别知乎用户数据爬取与分析
2016/01/22 PHP
php等比例缩放图片及剪切图片代码分享
2016/02/13 PHP
CSS+Table图文混排中实现文本自适应图片宽度(超简单+跨所有浏览器)
2009/02/14 Javascript
在次封装easyui-Dialog插件实现代码
2010/11/14 Javascript
jQuery :first选择器使用介绍
2013/08/09 Javascript
javascript与jquery中跳出循环的区别总结
2013/11/04 Javascript
js获取鼠标点击的位置实现思路及代码
2014/05/09 Javascript
常用的jQuery前端技巧收集
2014/12/24 Javascript
JavaScript使用ActiveXObject访问Access和SQL Server数据库
2015/04/02 Javascript
基于Bootstrap的Metronic框架实现页面链接收藏夹功能
2016/08/29 Javascript
JS编写函数实现对身份证号码最后一位的验证功能
2016/12/29 Javascript
在vue中获取dom元素内容的方法
2017/07/10 Javascript
Vue服务器渲染Nuxt学习笔记
2018/01/31 Javascript
微信小程序实现美团菜单
2018/06/06 Javascript
vuex actions传递多参数的处理方法
2018/09/18 Javascript
2019 年编写现代 JavaScript 代码的5个小技巧(小结)
2019/01/15 Javascript
Node.js API详解之 V8模块用法实例分析
2020/06/05 Javascript
逐行分析鸿蒙系统的 JavaScript 框架(推荐)
2020/09/17 Javascript
vue实现简单的登录弹出框
2020/10/26 Javascript
python操作gmail实例
2015/01/14 Python
python求解水仙花数的方法
2015/05/11 Python
Python的Django框架中的URL配置与松耦合
2015/07/15 Python
Python下rrdtool模块的基本使用方法
2015/11/13 Python
Python实现连接postgresql数据库的方法分析
2017/12/27 Python
Django开发的简易留言板案例详解
2018/12/04 Python
一篇文章搞懂Python的类与对象名称空间
2018/12/10 Python
Python GUI之tkinter窗口视窗教程大集合(推荐)
2020/10/20 Python
python 实现围棋游戏(纯tkinter gui)
2020/11/13 Python
实习自我鉴定
2013/12/15 职场文书
竞选纪律委员演讲稿
2014/09/13 职场文书
保研推荐信范文
2015/03/25 职场文书
退伍军人感言
2015/08/01 职场文书
《吃水不忘挖井人》教学反思
2016/02/22 职场文书
新手入门Mysql--概念
2021/06/18 MySQL