在web開發過程中經常會碰到需要選擇日期的功能,一般的操作都是在文字框點選,然後彈出日曆選擇框,直接選擇日期就可以在文字框顯示選擇的日期。開發好之後給使用者使用是很方便,但如果每一個日曆選擇器都要臨時開發的話,會嚴重影響專案進度。所以網上有很多日曆外掛提供下載使用。
在實際工作中,日曆選擇器功能確實都是直接使用已開發好的外掛。但作為一個前端工程師,還是需要知道具體例項方法,也應該有能力完成此類功能。本例項教程詳細講解怎麼樣使用原生js,通過物件導向來開發一個日曆選擇器外掛。
一般日曆外掛使用都非常簡單,只需要提供一個input元素就行,其他的工作都是通過外掛來完成。本例項也先準備一個input元素待用,如下所示:
<div style="text-align:center;"> <input type="text" id="calendarInput"> </div>
在這裡附上css樣式,讀者也可以根據喜好自己編寫樣式。css程式碼如下所示:
*{box-sizing:border-box;} .calendar_wrap{position:relative;display:inline-block;margin:100px auto;} .calendar_wrap a{color:#333;text-decoration:none;cursor:pointer;} .calendar_container input{ width:100%;border:1px solid #ccc;border-radius:4px;line-height:30px; font-size:14px;color:#333;outline:none;padding-left:6px; } .calendar_container .calendar_clean{position:absolute;top:6px;right:6px;z-index:1;display:none;cursor:pointer;} .calendar_container .calendar_clean:after{content:"\E62F";font-family:"iconfont";font-size:16px;display:inline-block;color:#ccc;} .calendar_container .calendar_icon{position:absolute;top:6px;right:6px;pointer-events:none;display:block;} .calendar_container .calendar_icon::after{content:"\E646";font-family:"iconfont";font-size:16px;display:inline-block;color:#aaa;} .calendar_container:hover .calendar_clean{display:block;} .calendar_container:hover .calendar_icon{display:none;} .calendar_main{ position:absolute;top:36px;left:0px;width:220px;box-shadow:0px 0px 3px #ccc; border-radius:4px;z-index:2;background:#fff; } .calendar_main .calendar_head{font-size:0;} .calendar_main .calendar_head a{ display:inline-block;height:32px;line-height:32px; text-align:center;font-size:12px;vertical-align:middle; } .calendar_main .calendar_head .chang_btn{width:30px;padding-top:1px;} .calendar_main .calendar_head .year_month_btn{width:64px;margin:0 48px;white-space:nowrap;} .calendar_main .calendar_head .year_btn,.calendar_main .calendar_head .month_btn{width:50px;} .calendar_main .calendar_head .chang_btn::after{content:"";display:inline-block;height:0;width:0;border:6px solid transparent;} .calendar_main .calendar_head .chang_btn::before{content:"";display:inline-block;height:0;width:0;border:6px solid transparent;} .calendar_main .calendar_head .left_year_btn::after{border-right-color:#aaa;margin-left:-14px;} .calendar_main .calendar_head .left_year_btn:hover::after{border-right-color:#f60;} .calendar_main .calendar_head .right_year_btn::after{border-left-color:#aaa;margin-left:-14px;} .calendar_main .calendar_head .right_year_btn:hover::after{border-left-color:#f60;} .calendar_main .calendar_head .left_month_btn::after{border-right-color:#fff;margin-left:-10px;} .calendar_main .calendar_head .left_month_btn::before{border-right-color:#aaa;} .calendar_main .calendar_head .left_month_btn:hover::before{border-right-color:#f60;} .calendar_main .calendar_head .right_month_btn::after{border-left-color:#fff;margin-left:-14px;} .calendar_main .calendar_head .right_month_btn::before{border-left-color:#aaa;} .calendar_main .calendar_head .right_month_btn:hover::before{border-left-color:#f60;} .calendar_main .calendar_head a:hover{color:#f60;} .calendar_main .calendar_body{border-top:1px solid #ccc;border-bottom:1px solid #ccc;} .calendar_main .calendar_body table{width:100%;table-layout:fixed;} .calendar_main .calendar_body th{text-align:center;line-height:20px;font-size:12px;} .calendar_main .calendar_body td{padding:0 4px;height:24px;text-align:center;font-size:12px;} .calendar_main .calendar_body td a{display:inline-block;width:20px;height:20px;cursor:pointer;line-height:20px;color:#ccc;} .calendar_main .calendar_body .cur_month{color:#666;background:inherit;} .calendar_main .calendar_body .cur_day{ border-radius:2px;font-weight:bold;line-height:18px;background:#eee;color:#000 } .calendar_main .calendar_body .sel_day{color:#fff;background:#f60;border-radius:2px;} .calendar_main .calendar_foot{padding:6px 10px;text-align:right;} .calendar_main .calendar_foot .define_btn{ border-radius:4px;line-height:24px;height:24px;border:0; background:none;color:#666;cursor:pointer;outline:none; } .calendar_main .calendar_foot .define_btn:hover{color:#f60;} .calendar_main .calendar_body ul{font-size:0;margin:0;padding:0;} .calendar_main .calendar_body li{display:inline-block;width:55px;height:60px;line-height:54px;font-size:14px;} .calendar_main .calendar_body li a{padding:8px;} .calendar_main .calendar_body li .no_cur{color:#ccc;}
接下來開始封裝日曆選擇器外掛。新建 calendar.js 檔案,接下來的外掛程式碼都寫在此檔案中。
需要在body元素中引入 calendar.js 檔案,如下所示:
<script src="js/calendar.js"></ script>
把日曆選擇器功能拆分成一個一個的步驟來完成。
//建立日曆外掛建構函式 function CalendarPlugin(elem,opt={}){ }
<script src="js/calendar.js"></script> <script> //獲取文字框 var eCalendarInput = document.getElementById('calendarInput'); //配置選項 var oConfig = { } //呼叫日曆建構函式生成例項物件 var oCalenderObj = new CalendarPlugin(eCalendarInput,oConfig); </script>
//建立日曆外掛建構函式 function CalendarPlugin(elem,opt={}){ //執行初始化 this.init(); } //初始化 CalendarPlugin.prototype.init = function(){ }
//建立日曆外掛建構函式 function CalendarPlugin(elem,opt={}){ //把文字框設定為日曆物件的屬性 this.eInput = elem; /*...*/ } //初始化 CalendarPlugin.prototype.init = function(){ //獲取原有intpu元素的父元素 var eInputParent = this.eInput.parentNode; //建立日曆包裹元素並新增class名稱 var eWrap = document.createElement('div'); eWrap.className = 'calendar_wrap'; //建立文字框容器元素 var eInputContainer = document.createElement('div'); eInputContainer.className = 'calendar_container'; //建立清除按鈕 var eClear = document.createElement('div'); eClear.className = 'calendar_clean'; //建立日曆圖示元素 var eIcon = document.createElement('div'); eIcon.className = 'calendar_icon'; //把日曆包裹放到原有父元素中 eInputParent.appendChild(eWrap); //文字框容器放到包裹中 eWrap.appendChild(eInputContainer); //把相關元素放到文字框容器中 eInputContainer.appendChild(this.eInput); eInputContainer.appendChild(eClear); eInputContainer.appendChild(eIcon); //設定文字框為只讀 this.eInput.setAttribute('readonly',true); }
此時文件框已經變成日曆選擇器的樣式了,如圖所示:
3.2 在init方法中建立彈出日曆選擇框元素
CalendarPlugin.prototype.init = function(){ /*...*/ //建立主要日曆容器元素 this.eMain = document.createElement('div'); this.eMain.className = 'calendar_main'; //把日曆容器放到包裹元素中 eWrap.appendChild(this.eMain); }
3.3 在日曆選擇框中新增頭部元素、主體元素和底部元素,程式碼如下:
CalendarPlugin.prototype.init = function(){ /*...*/ //建立日曆頭部 this.eHead = document.createElement('div'); this.eHead.className = 'calendar_head'; //把日曆頭部放到日曆容器中 this.eMain.appendChild(this.eHead); //設定當前年份 this.nYear = null; //設定當前月份 this.nMonth = null; //設定日曆模式,預設顯示日曆 this.sModel = 'date'; //建立日曆主體 this.eBody = document.createElement('div'); this.eBody.className = 'calendar_body'; //把日曆主體放到日曆容器中 this.eMain.appendChild(this.eBody); //當前選定日期 this.selDate = null; //建立底部元素 this.eFoot = document.createElement('div'); this.eFoot.className = 'calendar_foot'; this.eDefine = document.createElement('button'); this.eDefine.className = 'define_btn'; //把底部元素放到日曆容器中 this.eMain.appendChild(this.eFoot); this.eFoot.appendChild(this.eDefine); this.eDefine.innerHTML = '今 天'; }
此時效果如圖所示:
只有底部有一個“今 天”的按鈕,頭部年/月和主要的日期都還沒有,接下來開始建立這些元素
3.4 建立日曆選擇框中的頭部和日期元素
//初始化 CalendarPlugin.prototype.init = function(){ /*...*/ //生成日曆元素 this.generateDate(); } //建立頭部元素 CalendarPlugin.prototype.generateHead = function(){ //根據日曆模式不同,組成日曆頭部html程式碼 var sHeadHtml = '<a class="left_year_btn chang_btn" data-run="lessYear"></a>'; if(this.sModel == 'date'){ //日曆模式 sHeadHtml += `<a class="left_month_btn chang_btn" data-run="lessMonth"></a> <a class="year_btn" data-run="showYear" data-model="year">${this.nYear}年</a> <a class="month_btn" data-run="showMonth" data-model="month">${+this.nMonth+1}月</a> <a class="right_month_btn chang_btn" data-run="addMonth"></a>`; }else if(this.sModel == 'month'){ //月曆模式 sHeadHtml += `<a class="year_month_btn" data-run="showYear" data-model="year">${this.nYear}年</a>`; }else if (this.sModel == 'year'){ //年曆模式 sHeadHtml += `<a class="year_month_btn">${+(Math.floor(this.nYear/10)+'0')}- ${+(Math.floor(this.nYear/10)+'0')+10}</a>`; } sHeadHtml += '<a class="right_year_btn chang_btn" data-run="addYear"></a>'; //填充日曆頭部 this.eHead.innerHTML = sHeadHtml; } //生成日曆 CalendarPlugin.prototype.generateDate = function(date=null){ //組成日曆的html程式碼 var sBodyHtml = '<table>'; //組合週日 - 週六的列表頭 sBodyHtml += `<thead> <tr><th>日</th><th>一</th><th>二</th><th>三</th><th>四</th><th>五</th><th>六</th></tr> </thead>` /*日曆中需要記錄當前日期、選定日期、皮膚日期共三個時間*/ //當前日期:當前日期在日曆皮膚中有一個背景和加粗字型,需要新增class為cur_day var dCurDate = new Date(); //選定日期:在皮膚上選擇的日期,並且顯示在文字框中,有一個背景和白色字型,需要新增class為sel_day //選定日期需要記錄在日曆例項的selDate屬性上,如果暫無選定日期則為當前日期 var dSelDate = this.selDate || dCurDate; //初始化當前年/月 this.nYear = this.nYear || dSelDate.getFullYear(); this.nMonth = this.nMonth || dSelDate.getMonth(); //皮膚上顯示的日曆 var dShowDate = new Date(this.nYear,this.nMonth,dSelDate.getDate()); /* 日曆皮膚規則: 顯示42天,6行7列; 皮膚上的第一天是當月1號往前推到星期日。比如當月1號是星期一則上月顯示1天、星期三上月顯示3天、星期日上月顯示7天; */ //計算上月要顯示的天數 var nFrontNum = new Date(this.nYear,this.nMonth,1).getDay() || 7; //日曆皮膚上的日期每增加一個迴圈週期是一天,獲取一天的毫秒數 var cycle = 1000*60*60*24; sBodyHtml += '<thbody>' //迴圈42次 for(let i=1;i<43;i++){ //以下公式獲取日曆中每天遞增日期的時間戳 let dTimes = +dShowDate + cycle * (i-nFrontNum-dShowDate.getDate()); //通過時間戳建立Date例項物件 let dNewDate = new Date(dTimes); //獲取日期新增到html中 if((i-1)%7==0){ //判斷是否需要換行 sBodyHtml += '<tr>'; } //判斷是否是選定日期,當前日期,皮膚當月日期,分別加上對應的class sBodyHtml += `<td><a data-time="${dTimes}" class="${ this.quiteDate(dNewDate,dSelDate)?'sel_day': this.quiteDate(dNewDate,dCurDate)?'cur_day': dNewDate.getMonth()==this.nMonth?'cur_month':'' }">${dNewDate.getDate()}</a></td>`; if(i%7==0){ //判斷是否需要結束表格行 sBodyHtml += '</tr>'; } } sBodyHtml += '</thbody></table>'; //填充日曆皮膚 this.eBody.innerHTML = sBodyHtml; //生成皮膚頭部 this.generateHead(); } //比較兩個日期是否為同一天 CalendarPlugin.prototype.quiteDate = function(d1,d2){ var format = 'yyyy-MM-dd'; return this.format(d1,format) == this.format(d2,format); } //格式化日期 CalendarPlugin.prototype.format = function(date,format){ //用於正規表示式的匹配 var o = { "M+" : date.getMonth()+1, //月 "d+" : date.getDate(), //日 "h+" : date.getHours(), //小時 "m+" : date.getMinutes(), //分鐘 "s+" : date.getSeconds(), //秒 }; //使用正則將yyyy替換為當前年份 if(/(y+)/.test(format)){ format = format.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length)); } //列舉o物件中匹配的正則,比如MM替換當前月份,dd替換為當前日期 for(var k in o) { if(new RegExp("("+ k +")").test(format)){ format = format.replace(RegExp.$1, RegExp.$1.length==1 ? o[k] : ("00"+ o[k]).substr((""+ o[k]).length)); } } //把格式替換為正確日期後返回 return format; }
此時效果如圖所示:
到這一步,日曆選擇器的元素大部分都已經建立。這段程式碼新加了幾個方法,分別用於建立頭部、日期等,因為我都寫了詳細的註釋,這裡就不再贅述。接下來需要給元素繫結事件,實現日期變化和選擇等。
4. 新增配置選項
外掛一般都會允許開發人員根據專案需求做個性化設定,所以可以自行修改配置,比如設定初始日期、文字框提示、日期格式等。比如在輸入框新增提示,可以修改配置選項程式碼如下:
//獲取文字框 /*...*/ //配置選項 var oConfig = { placeholder:'請選擇日期', } //呼叫日曆建構函式生成例項物件 /*...*/
修改構建函式,在裡面新增預設配置選項程式碼,如下所示:
//建立日曆外掛建構函式 function CalendarPlugin(elem,opt={}){ //把文字框設定為日曆物件的屬性 /*...*/ //預設配置選項 this.oConfig = { format:'yyyy-MM-dd', //日期格式 value:null, //預設日期 placeholder:'' //文字框提示 } //修改為傳入的配置選項 for(let k in this.oConfig){ opt[k] && (this.oConfig[k] = opt[k]); } //執行初始化 /*...*/ }
在初始化方法中設定相關配置
//初始化 CalendarPlugin.prototype.init = function(){ /*...*/ //預設賦值 this.oConfig.value && (this.eInput.value = this.oConfig.value) && (this.selDate = new Date(this.oConfig.value)); //設定文字框提示 this.eInput.placeholder = this.oConfig.placeholder; }
此時可以看到輸入框中就有了預設提示資訊,如圖所示:
5. 新增繫結事件
5.1 輸入框繫結點選事件,點選顯示日曆皮膚
預設情況下,應該先把日曆皮膚設定隱藏
CalendarPlugin.prototype.init = function(){ /*...*/ //預設隱藏日曆皮膚 this.eMain.style.display = 'none'; }
再給輸入框新增事件,生成並顯示日曆。this.generateDate() 方法也應該放到事件中再呼叫,程式碼中有詳細註釋:
//初始化 CalendarPlugin.prototype.init = function(){ /*...*/ //文字框點選顯示日曆皮膚 this.eInput.addEventListener('click',()=>{ if(this.eMain.style.display=='none'){ //顯示日曆皮膚 this.eMain.style.display = 'block'; //在頁面上繫結點選事件,除日曆皮膚以外任何位置點選滑鼠時,隱藏日曆皮膚 document.addEventListener('click',hideMain,false); //預設顯示日曆 this.sModel = 'date'; //初始化年/月 this.nYear = null; this.nMonth = null; //生成日曆 this.generateDate(); }else{ //隱藏日曆皮膚 hideMain(); } }); //因為addEventListener監聽事件必須是命名函式才能取消,所以在這裡建立一個隱藏日曆皮膚函式 var eMain = this.eMain; function hideMain(){ eMain.style.display = 'none'; document.removeEventListener('click',hideMain,false); } //阻止冒泡 eWrap.addEventListener('click',function(event){ event.stopPropagation(); }); }
此時已經實現輸入框點選顯示日曆選擇框,空白位置點選隱藏日曆選擇框功能。
5.2 日期皮膚繫結點選事件,選擇日期或今天按鈕,修改文字框的值,並且隱藏日曆皮膚
//初始化 CalendarPlugin.prototype.init = function(){ /*...*/ //日期皮膚點選事件 this.eBody.addEventListener('click',(event)=>{ //獲取點選的元素 let eTarget = event.target; //獲取日期時間戳 let sTime = eTarget.dataset.time; //獲取月 let sMonth = eTarget.dataset.month; //獲取年 let sYear = eTarget.dataset.year; //獲取當前元素className let sClass = eTarget.className; if(this.sModel=='date'){ //當前模式是日期,在輸入框顯示日期,並隱藏日曆皮膚 if(sTime && sClass != 'sel_day'){ this.selDate = new Date(+sTime); this.eInput.value = this.format(this.selDate,this.oConfig.format); hideMain(); } }else{ if(sMonth||sYear){ //年曆或月曆皮膚,建立選擇的年或月的日期 this.nYear = sYear || this.nYear; this.nMonth = sMonth || this.nMonth; this.sModel = 'date'; this.generateDate(); } } }); //點選今天按鈕選擇今天的日期 this.eDefine.addEventListener('click',(event)=>{ this.selDate = new Date(); this.eInput.value = this.format(this.selDate,this.oConfig.format); hideMain(); }); }
此時選擇日期後效果如下所示:
5.3 頭部元素繫結點選事件,例項修改年、月,選擇年、月等功能,程式碼如下:
//初始化 CalendarPlugin.prototype.init = function(){ /*...*/ //日曆皮膚頭部點選事件 this.eHead.addEventListener('click',(event)=>{ //獲取點選的元素 let eTarget = event.target; //獲取修改方式 let sRun = eTarget.dataset.run; //獲取皮膚模式 let sModel = eTarget.dataset.model; this.sModel = sModel || this.sModel; if(sRun=='addYear'){ //切換後一年 if(this.sModel=='year'){ this.nYear+=10; }else{ this.nYear++; } }else if(sRun=='lessYear'){ //切換前一年 if(this.sModel=='year'){ this.nYear-=10; }else{ this.nYear--; } }else if(sRun=='addMonth'){ //切換下一個月 this.nMonth++; }else if(sRun=='lessMonth'){ //切換前一個月 this.nMonth--; } if(this.sModel=='year'){ this.generateYear(); }else if(this.sModel=='month'){ this.generateMonth(); }else{ this.generateDate(new Date(this.nYear,this.nMonth,1)); //因為切換隻年月沒選擇日期,日期可以任意一天,所以設定為1號 } }); } /*...*/ //生成月曆 CalendarPlugin.prototype.generateMonth = function(){ //生成月份的html元素 let sBodyHtml = '<ul>'; for(let i=0;i<12;i++){ sBodyHtml += `<li><a data-month="${i+1}" class="${i==this.nMonth?'sel_day':''}">${i+1}月</a></li>` } sBodyHtml += '</ul>'; this.eBody.innerHTML = sBodyHtml; //生成皮膚頭部 this.generateHead(); } //生成年曆 CalendarPlugin.prototype.generateYear = function(){ //共顯示12年,可以通過把當前年最後一個數字改為0獲取10年中的第一年 let nStart = +(Math.floor(this.nYear/10)+'0'); //再從-1開始迴圈到11,就可以迴圈出12年 //生成年份的html元素 let sBodyHtml = '<ul>'; for(let i=-1;i<11;i++){ sBodyHtml += `<li><a data-year="${i+nStart}" class="${ i==-1||i==10?'no_cur': i+nStart==this.nYear?'sel_day':'' }">${i+nStart}</a></li>` } sBodyHtml += '</ul>'; //修改皮膚 this.eBody.innerHTML = sBodyHtml; //生成皮膚頭部 this.generateHead(); }
日期選擇器功能已經基本完成,這已經是一個可以在專案中正常使用的日期選擇器,如有疑問或bug,歡迎在留言中提出,感謝!