原生JS實現(xiàn)日歷組件的示例代碼
想要實現(xiàn)的效果
- 點擊日期選擇框出現(xiàn)日歷
- 有個日期控制欄幫助選擇日期, 包括年、月、日的選擇和今天
- 日歷格子,初次點擊日期選擇框時顯示此刻的日期,日歷格子的日期應該包括這個月的所有天數(shù),同時如果當月的1號不是周日,還應補全從周日到1號的天數(shù)。還要在這個月最后1號的后面補全到周六。
- 日期控制欄和日歷格子的日期還有選擇框里的日期的變化要是同步的。
實現(xiàn)思路
為了組件的可復用性,需要用面向對象的思想。
每個日歷組件都是一個日歷對象,主要包括日期選擇框,日期控制顯示欄,還有日歷格子,為了保持日期控制顯示欄和日歷格子日期同步變化,日期控制欄和日歷里面的每個格子都應該包含一個Date屬性,點擊日歷里的格子,將格子存的Date屬性作為函數(shù)參數(shù),調用函數(shù)改變日期控制欄顯示的時間。同理,日期控制欄時間變化時,也將Date屬性作為參數(shù)調用函數(shù),函數(shù)重新繪制日歷格子。
上碼:
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}; //讓按鈕點擊時不會出現(xiàn)文字被選中的藍色塊
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之后,初始化,傳入的參數(shù)是現(xiàn)在的時間
var yearInput = parentElement.firstChild;
//年選擇框點擊顯示和隱藏選擇列表
yearInput.onclick = function(){ //target和this的區(qū)別 target是觸發(fā)事件的元素,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); //時間改變之后都要重新調用,因為不同年,不同月,某個月的天數(shù)不全一樣
};
}
//為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函數(shù)為日期控制欄的年份、月份、和天的點擊按鈕設置了點擊事件處理函數(shù)。還有選擇下拉框變化的處理函數(shù)。
在日期控制欄初始化時,或者改變showDateBar的Date時,都會調用changeShowDateBar 函數(shù)。這個函數(shù)主要根據(jù)傳入的日期改變日期控制欄“日”下拉欄的天數(shù),因為每個月的天數(shù)不盡相同,所以要根據(jù)傳入的日期來改變。會計算出傳入的日期對應的月份有多少天,使用getDaysOfMonth函數(shù)計算。
//計算一個月的天數(shù)
getDaysOfMonth: function(primalDate) {
var date = new Date(primalDate); //要新建一個對象,因為會改變date
var month = date.getMonth();
var time = date.getTime(); //計算思路主要是month+1,相減除一天的毫秒數(shù)
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函數(shù)結尾處調用drawPicker函數(shù),重新繪制日歷格子。
繪制日歷格子的思路
drawPicker函數(shù)要根據(jù)傳入的日期繪制日歷格子。
- 首先計算傳入的日期月份的天數(shù)
- 計算這個月1號是周幾 。利用Date對象的date.setDate(1) //將天設置為1號 。date.getDay() //得到這天是周幾
- 如果1號不是周日,則補全周日到1號的天數(shù)??梢岳胦ldDate.setDate(-1) //設置日期為原來日期的上個月的最后一天。注意setDate是會改變當前日期的,并不是返回新的日期。
- 從1號到這個月最后一天循環(huán)。
- 補全最后一天到周六的天數(shù)
drawPicker函數(shù):
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); //計算出這個月的天數(shù)
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);
},
實現(xiàn)效果

有點丑......
實現(xiàn)中遇到的問題
- 日歷格子的繪制問題 。要補全1號前面到周日的天數(shù),還要補全當月最后1號到周六的天數(shù)。日歷格子的繪制可以分為3部分,當月前面、當月和當月后面的。要計算出1號是周幾,然后將這周周日到1號的天數(shù)繪制。
- 當月的日歷從1號到最后1號循環(huán)繪制。補全最后1號到周六的天數(shù)(date.getDay()<=6)
- 日歷格子和日期控制欄顯示的同步。在繪制時為每個日歷格子單元保存其代表的Date。點擊格子單元時,調用changeShowDateBar函數(shù),將單元存的Date傳入,改變日期控制欄顯示的日期,然后重繪日歷格子。
- 每個月天數(shù)不同,出現(xiàn)的“日”選擇框天數(shù)不同的問題。在changeShowDateBar函數(shù)里會根據(jù)傳入的Date,計算當月有多少天,然后動態(tài)生成“日”選擇框應有的天數(shù)。
- 跨月,跨年的處理。在日期控制欄中,有月份和日的上下按鈕,在處理跨月和跨年時,判斷這月(日)是否為最后一月(日),若為,則日期控制欄的Date的年(月)加1,將顯示的月(日)設為第一月(日),調用changeShowDateBar函數(shù)。同理判斷是否為第一月(日)。
用到的Date API
- date.getFullYear() //得到date的年份
- date.getMonth() //得到月份 0-11
- date.getDate() //得到日期 1-31的數(shù)字
- 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個月
- 如果為負數(shù),例如-1則會調到上一年的最后一月去。
- date.setDate(x) // 和setMonth是同理的,它會自動根據(jù)當月的天數(shù),判斷是否發(fā)生月份的變動。-1代表date跳到上月的最后一天
- date.setTime()// 根據(jù)時間戳設置date
項目源碼 https://github.com/wenkeShi/js-calendar
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
JavaScript使用localStorage判斷設置值是否過期
本文主要介紹了JavaScript使用localStorage判斷設置值是否過期,通過設置過期時間,我們可以使用 setItemWithExpiration 函數(shù)將數(shù)據(jù)存儲到 localStorage 中,并使用 getItemWithExpiration 函數(shù)獲取數(shù)據(jù)并檢查是否過期,感興趣的可以了解一下2023-05-05
(JS實現(xiàn))MapBar中坐標的加密和解密的腳本
(JS實現(xiàn))MapBar中坐標的加密和解密的腳本...2007-05-05

