近期在知乎上看到这么一个帖子,题主说自己JavaScript都学完了,结果老师留的作业还是不会写,就是写一个日历的插件,结果楼下一堆大牛出现了,百度的阿里的纷纷站出来发表自己的看法,有人认为简单,有人认为其实细化不简单,于是出于好奇,自己也来动手写了一下。虽说现如今各种优秀的UI框架层出不穷,都会自带calendar和datepick这种日历相关的组件,而且无论是适配还是视觉效果都做得相当nice,可能都不会用到自己写的,但是还是打算动手,因为date对象也是js里面的重点。

第一版:纯js实现
通过切换月份和年份,来展示不同的日历页面,实际上是根据当前年月,来进行页面的重绘,所以页面渲染是一个函数,所需参数就是当前选中的年份和月份。
const render = (month, year) => {
};
render();
首先,日历除去首行day有几行?
一共6行,Math.ceil((31 - 1) / 7 + 1) = 6
三块组成:上月剩余,本月,下月开始
1,上月剩余
首先要知道本月1号是周几(x),然后从周日到x的天数就是上月剩余天数,从几号到几号,需要了解本月是几月,来推算出上月月末是几号,其实也就是上月有多少天。
2,本月
只需知道当月是几月,当月多少天,然后按顺序排。
3,下月开始
只需要知道下月1号是周几,然后42个数字还剩多少,从1排到最后就可以了
 
代码部分:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Date Picker</title>
<link rel="stylesheet" type="text/css" href="css/index.css"></link>
</head>
<body>
<input id="textInput" class="textInput" type="text" placeholder="选择日期" />
<div id="datePicker" class="datePicker datePickerHide">
<div class="dateHeader">
<span id="yearLeft" class="left headerMid"><<</span>
<span id="monthLeft" class="left headerMid page"><</span>
<span id="changeDateHead" class="page">
<span id="changeYear" class="headerMid col">
<span id="dateYear"></span>
<span>年</span>
</span>
<span id="changeMonth" class="headerMid col">
<span id="dateMonth"></span>
<span>月</span>
</span>
</span>
<span id="changeYearHead" class="page headerMid col" style="display: none">
<span id="changeYearFirst"></span>
<span>年</span>
<span>-</span>
<span id="changeYearLast"></span>
<span>年</span>
</span>
<span id="changeMonthHead" class="page headerMid col" style="display: none">
<span id="backChangeYearPage"></span>
<span>年</span>
</span>
<span id="yearRight" class="right headerMid">>></span>
<span id="monthRight" class="right headerMid page">></span>
</div>
<div class="dateMain">
<div id="firstPage" class="page firstPage" style="display: block">
<div class="dateDay">
<span>日</span>
<span>一</span>
<span>二</span>
<span>三</span>
<span>四</span>
<span>五</span>
<span>六</span>
</div>
<div id="dateBody" class="dateBody"></div>
</div>
<div id="secondPage" class="page secondPage" style="display: none"></div>
<div id="thirdPage" class="page secondPage" style="display: none" onclick="chooseMonth()">
<span><em id="month-1" index='1'>1月</em></span>
<span><em id="month-2" index='2'>2月</em></span>
<span><em id="month-3" index='3'>3月</em></span>
<span><em id="month-4" index='4'>4月</em></span>
<span><em id="month-5" index='5'>5月</em></span>
<span><em id="month-6" index='6'>6月</em></span>
<span><em id="month-7" index='7'>7月</em></span>
<span><em id="month-8" index='8'>8月</em></span>
<span><em id="month-9" index='9'>9月</em></span>
<span><em id="month-10" index='10'>10月</em></span>
<span><em id="month-11" index='11'>11月</em></span>
<span><em id="month-12" index='12'>12月</em></span>
</div>
</div>
</div>
</body>
<script src="js/index.js"></script>
</html>

  

js部分:

    // 默认是当天
var chosenDate = new Date(),
year = chosenDate.getFullYear(),
month = chosenDate.getMonth() + 1,
date = chosenDate.getDate(),
lastDateId = '', // 挂到全局下去
lastYearId = '',
lastMonthId = '',
firstNum = 0; window.onload = function(){ renderFirstPage(year, month, date); // input框获取焦点时日历显示 var datePicker = getIdDom('datePicker'); getIdDom('textInput').onfocus = function(){
datePicker.className = 'datePicker datePickerShow';
} /* 以上是第一部分页面展示 */ /* 二级页面 */
var renderSecondPage = function(firstNum){
var lastNum = firstNum + 9; // 二级页面末尾数字
getIdDom('changeYearFirst').innerHTML = firstNum;
getIdDom('changeYearLast').innerHTML = lastNum; var yearTemplate = ``;
for (var i = firstNum; i < lastNum + 1; i++) {
if (i == year) {
// 当前选中年
yearTemplate += `<span><em id="year-${i}" onclick="chooseYear(this, ${i})" style="background-color: #39f;color: #fff">${i}</em></span>`
} else {
yearTemplate += `<span><em id="year-${i}" onclick="chooseYear(this, ${i})">${i}</em></span>`
}
}
getIdDom('secondPage').innerHTML = yearTemplate;
} var reRenderSecondPage = function(){
var yearStr = year.toString();
var yearLastLetter = yearStr[yearStr.length-1]; // 末尾数
firstNum = year - Number(yearLastLetter); // 二级页面首位数字
renderSecondPage(firstNum);
}
reRenderSecondPage(); getIdDom("backChangeYearPage").innerHTML = year; // 三级页面年份 // click事件集中写 // 上一年下一年,上一月下一月的点击事件
clickFn('yearLeft', function(){
if (getIdDom('changeYearHead').style.display != 'none') {
// 此时是二级页面,选年份
if (firstNum - 10 < 1) {
// 首位年份不能小于1
return
}
firstNum -= 10;
renderSecondPage(firstNum);
} else {
if (year - 1 < 1) {
// 年份不能小于1
return
}
year--;
renderFirstPage(year, month, date);
getIdDom("backChangeYearPage").innerHTML = year;
reRenderSecondPage();
}
}); clickFn('monthLeft', function(){
if (month < 2) {
// 1月
month = 12;
year--;
} else {
month--;
}
renderFirstPage(year, month, date)
}); clickFn('yearRight', function(){
if (getIdDom('changeYearHead').style.display != 'none') {
// 此时是二级页面,选年份
firstNum += 10;
renderSecondPage(firstNum);
} else {
year++;
renderFirstPage(year, month, date);
getIdDom("backChangeYearPage").innerHTML = year;
reRenderSecondPage();
}
}); clickFn('monthRight', function(){
if (month > 11) {
// 12月
month = 1;
year++;
} else {
month++;
}
renderFirstPage(year, month, date)
}); clickFn('changeYear', function(){
var pagesArr = Array.from(document.querySelectorAll('.page'));
pagesArr.forEach(function(item){
item.style = 'display: none'
});
reRenderSecondPage();
changeStyle('secondPage', 'display: block');
changeStyle('changeYearHead', 'display: inline-block');
}); // 点击年份切换至二级页面 clickFn('changeMonth', function(){
var pagesArr = Array.from(document.querySelectorAll('.page'));
pagesArr.forEach(function(item){
item.style = 'display: none'
});
if (lastMonthId !== '') {
// 非第一次点击
getIdDom(lastMonthId).style = "";
}
changeStyle("month-" + month, 'background-color: #39f;color: #fff');
lastMonthId = 'month-' + month;
changeStyle('thirdPage', 'display: block');
changeStyle('changeMonthHead', 'display: inline-block');
}) clickFn('changeMonthHead', function(){
// 切回年份选择
var pagesArr = Array.from(document.querySelectorAll('.page'));
pagesArr.forEach(function(item){
item.style = 'display: none'
});
changeStyle('secondPage', 'display: block');
changeStyle('changeYearHead', 'display: inline-block');
reRenderSecondPage();
}) document.getElementsByTagName('html')[0].onclick = function(e){
// 这里模拟失去焦点事件
var name = e.target.nodeName;
if (name == 'BODY' || name == 'HTML') {
datePicker.className = 'datePicker datePickerHide';
}
} } function getIdDom(id){
return document.getElementById(id)
} // 根据id获取dom节点 function clickFn(id, fn) {
getIdDom(id).onclick = fn;
} // 封装一下click方法 function renderFirstPage(year, month, date = 1){
var datePage = []; // 最终展示页面的所有日期合集
// 第一部分,上月月末几天
// 首先要知道上月一共多少天
var getAlldays = (year, month) => {
if (month <= 7) {
// 1-7月
if (month % 2 === 0) {
// 偶数月
if (month === 2) {
// 2月特殊
if ((year % 400 == 0) || ( year % 4 == 0 && year % 100 != 0)) {
// 闰年
var alldays = 29
} else {
var alldays = 28
}
} else {
var alldays = 30
}
} else {
// 奇数月
var alldays = 31
}
} else {
// 8-12月
if (month % 2 === 0) {
// 偶数月
var alldays = 31
} else {
var alldays = 30
}
}
return alldays
}; // alldays表示某年某月的总天数
var lastMonthAllDays = getAlldays(year, month - 1); // 上月天数
var chosenFirstMonthDay = new Date(year, month - 1, 1).getDay(); // 当月1号周几 var datePageTemplate = ``;
var num = 0; for (var i = lastMonthAllDays - chosenFirstMonthDay + 1; i < lastMonthAllDays + 1; i++ ) {
datePageTemplate += `<span id="lastMonth"><em id="last-${i}" onclick="chooseDate(this, 'last-${i}', ${year}, ${month}, ${i})">${i}</em></span>`;
num++;
} // 第二部分,当月总天数
var chosenMonthAllDays = getAlldays(year, month); // 当月天数
var time = new Date();
var a = time.getFullYear(),
b = time.getMonth() + 1,
c = time.getDate(); // 用来记录当天时间
for(var i = 1; i < chosenMonthAllDays + 1; i++) {
var chosenDateObj = {
year: year,
month: month,
date: i
};
if (i === c && year === a && month === b) {
// 今天日期高亮
datePageTemplate += `<span id="today" class="currentMonth"><em id="today-${i}" onclick="chooseDate(this, 'today-${i}', ${year}, ${month}, ${i})" class="today">${i}</em></span>`;
} else {
datePageTemplate += `<span id="currentMonth" class="currentMonth"><em id="cur-${i}" onclick="chooseDate(this, 'cur-${i}', ${year}, ${month}, ${i})">${i}</em></span>`;
} num++;
} // 第三部分,剩余天数
for (var i = 1; i < 43 - num; i++) {
var chosenDateObj = {
year: year,
month: month,
date: i
};
datePageTemplate += `<span id="nextMonth"><em id="nex-${i}" onclick="chooseDate(this, 'nex-${i}', ${year}, ${month}, ${i})">${i}</em></span>`;
} var templateString = `${datePageTemplate}`; var innerFn = function(id, content) {
getIdDom(id).innerHTML = content
};
innerFn('dateYear', year);
innerFn('dateMonth', month);
innerFn('dateBody', templateString); } function chooseDate(item, index, year, month, date) {
event.stopPropagation();
if (lastDateId !== '') {
// 非第一次点击
getIdDom(lastDateId).style = "";
}
// 选中项样式改变,并且将input的日期修改
lastDateId = index;
item.style = "background-color: #39f;color: #fff";
getIdDom("textInput").value = year + '-' + month + '-' + date;
} function chooseYear(item, thisYear) {
event.stopPropagation();
if (lastYearId !== '') {
// 非第一次点击
if (getIdDom(lastYearId)) {
// 存在已经跨页面了,但是id不存在了
getIdDom(lastYearId).style = "";
}
} else {
// 第一次点击
getIdDom('year-' + year).style = ""; }
lastYearId = 'year-' + thisYear;
year = thisYear;
item.style = "background-color: #39f;color: #fff";
var pagesArr = Array.from(document.querySelectorAll('.page'));
pagesArr.forEach(function(item){
item.style = 'display: none'
});
if (lastMonthId !== '') {
// 非第一次点击
getIdDom(lastMonthId).style = "";
}
changeStyle("month-" + month, 'background-color: #39f;color: #fff');
lastMonthId = 'month-' + month;
getIdDom("backChangeYearPage").innerHTML = year;
changeStyle('changeMonthHead', 'display: inline-block');
changeStyle('thirdPage', 'display: block');
} function chooseMonth(){
var target = event.target;
if (target.nodeName === 'EM') {
// 表示当前点击的为em节点
if (lastMonthId !== '') {
// 非第一次点击
getIdDom(lastMonthId).style = "";
} else {
// 第一次点击
getIdDom('month-' + month).style = "";
}
month = parseInt(target.innerHTML);
lastMonthId = 'month-' + month;
target.style = "background-color: #39f;color: #fff"; renderFirstPage(year, month, date); // 展示首页
var pagesArr = Array.from(document.querySelectorAll('.page'));
pagesArr.forEach(function(item){
item.style = 'display: none'
});
changeStyle('firstPage', 'display: block');
changeStyle(['changeDateHead', 'monthLeft', 'monthRight'], 'display: inline-block');
}
} // 判断对象类型
function isType(type){
return function(obj){
return toString.call(obj) == '[object ' + type + ']';
}
} // 改变display属性
function changeStyle(ids, styles){
var isString = isType('String'),
isArray = isType('Array');
if (isString(ids)) {
getIdDom(ids).style = styles;
} else if (isArray(ids)) {
ids.forEach(function(item){
getIdDom(item).style = styles;
})
}
}

  

  

css部分:

* {
margin: 0;
padding: 0;
} *, :after, :before {
box-sizing: border-box;
} body {
font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,\\5FAE\8F6F\96C5\9ED1,Arial,sans-serif;
} .textInput {
position: relative;
display: block;
} .datePicker {
width: 216px;
margin: 10px;
color: #c3cbd6;
border-radius: 4px;
box-shadow: 0 1px 6px rgba(0,0,0,.2);
transform-origin: center top 0px;
transition: all .2s ease-in-out;
position: absolute;
left: 0px;
top: 16px;
}
.datePickerHide {
opacity: 0;
} .datePickerShow {
opacity: 1;
} .dateHeader {
height: 32px;
line-height: 32px;
text-align: center;
border-bottom: 1px solid #e3e8ee;
} .left, .right {
display: inline-block;
width: 20px;
height: 24px;
line-height: 26px;
margin-top: 4px;
text-align: center;
cursor: pointer;
color: #c3cbd6;
-webkit-transition: color .2s ease-in-out;
transition: color .2s ease-in-out;
} .left {
float: left;
margin-left: 10px;
} .right {
float: right;
margin-right: 10px;
} .dateMain {
margin: 10px;
} .dateDay {
line-height: normal;
font-size: 0;
letter-spacing:normal;
} span {
display: inline-block;
text-align: center;
font-size: 12px;
} .dateDay span {
line-height: 24px;
width: 24px;
height: 24px;
margin: 2px;
} .dateBody span {
width: 28px;
height: 28px;
cursor: pointer;
} .dateBody span em {
display: inline-block;
position: relative;
width: 24px;
height: 24px;
line-height: 24px;
margin: 2px;
font-style: normal;
border-radius: 3px;
text-align: center;
transition: all .2s ease-in-out;
} .dateBody span.currentMonth {
color: #657180;
} .today:after {
content: '';
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
background: #39f;
position: absolute;
top: 1px;
right: 1px;
} .currentMonth em:hover {
background-color: #e1f0fe;
} .col {
color: #657180;
} .headerMid {
cursor: pointer;
} .headerMid:hover {
color: #39f;
} /* second */
.secondPage {
width: 196px;
font-size: 0;
} .secondPage span {
display: inline-block;
width: 40px;
height: 28px;
line-height: 28px;
margin: 10px 12px;
border-radius: 3px;
cursor: pointer;
} .secondPage span em {
display: inline-block;
width: 40px;
height: 28px;
line-height: 28px;
margin: 0;
font-style: normal;
border-radius: 3px;
text-align: center;
transition: all .2s ease-in-out;
color: #657180;
} .secondPage span em:hover {
background-color: #e1f0fe;
}

  

GitHub项目地址:https://github.com/Yanchenyu/DatePicker

 
 
项目写完了,但其实发现里面存在大量的DOM操作以及环境污染,这个对性能的损耗是相当大的,写得越多越发现状态难以管理,都只能挂到全局下去,所以打算再写一套React版本的。
 
 
 
 
 
 
 
 
 
 
end

最新文章

  1. ArcGIS Engine开发前基础知识(2)
  2. Android okHttp网络请求之Retrofit+Okhttp+RxJava组合
  3. EF架构~引入规约(Specification)模式,让程序扩展性更强
  4. Java Web之Servlet技术
  5. Big Data, MapReduce, Hadoop, and Spark with Python
  6. iOS之UI--辉光动画
  7. hdu 4185 二分图匹配
  8. 编译FreePascal和Lazarus
  9. 【C#】获取本地Cookie的问题
  10. Travel 并查集
  11. 前端基本知识(三):JS的闭包理解(第一个思考题有错误,已修改)
  12. 缓存数据库-redis数据类型和操作(list)
  13. VS2015密匙--VS2015打开丢失msvcp140.dll--cannot find one or more components ,please reinstall the application
  14. (转)RocketMQ源码学习--消息存储篇
  15. Android中Parcel的分析以及使用
  16. CS231n学习笔记-图像分类笔记(上篇)
  17. Git 管理本地代码【转】
  18. &#39;express&#39;不是内部或外部命令, 也不是可运行的程序, 或批处理文件
  19. keycloak 了解
  20. 系列文章--SQLite文章

热门文章

  1. 学习 MeteoInfo二次开发教程(五)
  2. vue仿淘宝结账订单
  3. 从零开始实现RPC框架 - RPC原理及实现
  4. JAVA方法参数传递
  5. Dubbo源码解析之registry注册中心
  6. spring cloud整合 websocket 的那些事
  7. python3 不知文件编码情况下打开文件代码记录
  8. 源码阅读经验谈-slim,darknet,labelimg,caffe(1)
  9. Keil中 Program Size: Code RO-data RW-data ZI-data
  10. Spring事务隔离级别和传播性