近期在知乎上看到這麼一個帖子,題主說本身JavaScript都學完了,結果老師留的做業仍是不會寫,就是寫一個日曆的插件,結果樓下一堆大牛出現了,百度的阿里的紛紛站出來發表本身的見解,有人認爲簡單,有人認爲其實細化不簡單,因而出於好奇,本身也來動手寫了一下。雖然說現現在各類優秀的UI框架層出不窮,都會自帶calendar和datepick這種日曆相關的組件,並且不管是適配仍是視覺效果都作得至關nice,可能都不會用到本身寫的,可是仍是打算動手,由於date對象也是js裏面的重點。javascript
初版:純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部分:css
// 默認是當天 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部分:html
* { 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/DatePickerjava
項目寫完了,但其實發現裏面存在大量的DOM操做以及環境污染,這個對性能的損耗是至關大的,寫得越多愈加現狀態難以管理,都只能掛到全局下去,因此打算再寫一套React版本的。
end