用javascript写一个日历
用javascript写一个日历
<!-- calendar container -->
<div class="calendar-container">
<div class="shrink-btn">-</div>
<h2>Calendar</h2>
<!-- calendar -->
<div class="calendar" id="calendar">
</div>
<!-- calendar end -->
<div class="calendar-btns">
<div class="calendar-btn btn">prev</div>
<a href="../calendar/calendar.html"><div class="calendar-btn btn">show all</div></a>
<div class="calendar-btn btn">next</div>
</div>
<div class="calendar-hidden-btn"></div>
</div>
<!-- calendar container end -->
1. 选择器
| selector | class | desc |
|---|---|---|
| calendarContainer | .calendar-container | 日历的显示块 |
| calendarContainerH2 | .calendar-container h2 | 日历的年份文本显示 |
| calendar | .calendar | 需要js插入日期的部分 |
| calendarHiddenBtn | .calendar-hidden-btn | 将日历显示的动画按钮 |
| shrinkBtn | .shrink-btn | 将日历隐藏的动画按钮 |
| calendarBtn | all(.calendar-btn) | 获取了三个按钮包括向上一个月,向下一个月和显示全部月份 |
2. 定义变量
获取的当前的日期,用以标注日历中的当前日期,比如今天是2020年8月17号,则日历中的这一天可以相应标记。
| 变量名 | 描述 |
|---|---|
| currentYear | 获取当前的年份 |
| currentCalendar | 获取当前的日期 |
| currentMonth | 获取当前的月份 |
| currentDate | 获取当前的日 |
| year | 要渲染的年份 |
| weekDays | 存储所有的星期字符串 |
| months | 存储所有的月份字符串 |
const currentCalendar = new Date();
const currentMonth = currentCalendar.getMonth();
const currentDate = currentCalendar.getDate();
const currentYear = currentCalendar.getFullYear();
let year = currentYear;
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'Octomber', 'November', 'December'];
日历以一年为单位来渲染,所以就需要一个函数参数来传递需要渲染的年份,function insertDate(year){},函数内容如下:
calendarContainerH2.innerHTML = `${year} Calendar`;
let dates = getAllDays(year);
let monthsHTML = '';
日历中的年份文本标题需要通过传入的年份参数动态更改,用dates绑定一个存储了一年中所有日期的数组,这个数组通过getAllDays函数获取,它也接收一个year年份参数,因为年份不同日历会不一样。monthsHTML存储一个html字符串用来插入每一个月份的html模板。
function getAllDays(year) {
// 获取这一年的第一天
const firstDay = new Date(`January 1 ${year}`);
// console.log(firstDay);
// 获取该年的最后一天
const lastDay = new Date(`December 31 ${year}`);
// console.log(lastDay);
// 获取这一年的所有日期,存入数组
const days = [firstDay];
// 追踪days数组是否已经存储完毕
let lastDayInArray = firstDay;
while (lastDayInArray.getTime() !==lastDay.getTime()) {
// addDays (需要增加的日期,增加的天数)
days.push(addDays(lastDayInArray, 1));
lastDayInArray = days[days.length - 1];
}
// console.log(days);
return days;
}
通过getAllDays函数获取一年中所有日期的数组,思路是获取这一年的第一天和最后一天,通过判断是否已经依次存入日期到最后一天来将所有日期存入数组,为了判断是否存入了最后一天,需要一个标记当前数组的最后一个元素,每次存入数组以后都将其与数组的最后一个元素绑定,在用它来与最后一天来判断。其中有一个addDays函数,它用来将天数加一天,以达到将每一天存入。
function addDays(date, days) {
let result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
Date.getDate()方法返回的是日,是number对应 1 - 31
months.forEach((months, index) => {
monthsHTML += `
<div class="months months_${index}">
<h3>${months}</h3>
<div class="week_days_container">
${weekDays.map((day) => `<div class="week_days">${day}</div>`).join('')}
</div>
<div class="days_container">
</div>
</div>`;
});
calendar.innerHTML = monthsHTML;
months数组中存储了一年的十二个月,通过foreach将数组中的每一个月遍历,叠加monthsHTML,会将12个月的html模板叠加入monthsHTML,其中每个月的星期都是7个,也需要循环加入,最后设置calendar的innerHTML。模板里的months_${index}可以更好地追踪月份对应地dom,days_container用来放置日节点。
dates.forEach((date) => {
// 获取月份
const month = date.getMonth();
// console.log(month);
// 获取月份对应的日期节点
const monthEl = document.querySelector(`.months_${month} .days_container`);
// 在每个月的第一天前面创建空白
// getDate() 返回月份的某一天 (1-31)
// getDay() 返回的是日期对应的星期 (0-6)
if (date.getDate() === 1 && date.getDay() !== 0) {
for (let i = 0; i < date.getDay(); i++) {
const emptySpot = createEmptySpot();
monthEl.appendChild(emptySpot);
}
}
const dateEl = createDateEl(date);
monthEl.appendChild(dateEl);
});
插入日的思路是,date中有月、星期、日,在同一个一个date元素中getMonth获取了月份,getDate就获取了该月的日期,从浏览器控制台看date就能清楚

,而因为每一个月的1号对应的星期都不一样,所以必须插入相应的空白节点,dates中存储了所有日期对象,为每一个日期对象获取月份,并选择对应的dom节点,这样就可以在对应的节点中插入对应的日。Date.getDay()方法返回的是星期,也是number,不过是以0开始,即 0-6,通过判断当日为一号,而且不为星期一时,因为如果一号就是星期一那么就不需要插入空白节点了,空白节点的数量是刚好与一号对应的星期数相同,比如一号对应的是星期三,即getDay是2,而日历中也是星期一和星期二两处空白。createEmptySpot就是用来创建空白节点的函数,然后把空白节点插入对应月份的节点。插入完空白的节点,就需要插入日节点了,createDateEl为创建日节点的函数,然后插入到对应的月份节点。
function createEmptySpot() {
const emptyEl = document.createElement('div');
emptyEl.classList.add('days');
return emptyEl;
}
上面函数创建的html模板是:
<div class="days"></div>
function createDateEl(date) {
const day = date.getDate();
const dateEl = document.createElement('div');
dateEl.classList.add('days');
dateEl.innerHTML = `<span class="circle date_${day}">${day}</span>`;
return dateEl;
}
这个函数用day接收日期的日,创建的html模板为:
<div class="days">
<span class="circle date_..">..</span>
</div>
以上就已经完成了渲染一整年日历的工作,但是一般都是以一个月显示并且可以来回切换月份和年份,我以function calendarAnimation(){}函数将这部分工作封装起来:
// 显示的月份
let displayMonth = currentMonth;
let displayMonthEl = document.querySelector(`.months_${displayMonth}`);
displayMonthEl.style.display = 'block';
// 日历显示动画
const monthsContainer = document.querySelectorAll('.months');
console.log(monthsContainer);
这部分为获取选择器,displayMonth用来标记需要显示到面板的月份,没有被标记的月份需要隐藏起来,需要显示的节点用displayMonthEl来动态获取。而monthsContainer是用来获取日历显示面板的,用来更好的实现面板的移动或隐藏。
calenarHiddenBtn.addEventListener('click', containerBack);
shrinkBtn.addEventListener('click', containerMove);
calendarBtn[0].addEventListener('click', monthChange);
calendarBtn[2].addEventListener('click', monthChange);
创建按钮点击事件,这里涉及到3个函数,containerBack函数用来显示日历面板,containerMove函数用来隐藏日历面板,monthChange用来切换显示的月份。
function monthChange(event) {
if (event.target === calendarBtn[0]) {
if (displayMonth >= 0 && displayMonth <= 11) {
displayMonthEl.style.display = 'none';
displayMonth--;
if (displayMonth < 0) {
year--;
insertDate(year);
displayMonth = 11;
}
displayMonthEl = document.querySelector(`.months_${displayMonth}`);
displayMonthEl.style.display = 'block';
}
} else {
if (displayMonth >= 0 && displayMonth <= 11) {
displayMonthEl.style.display = 'none';
displayMonth++;
if (displayMonth > 11) {
year++;
insertDate(year);
displayMonth = 0;
}
displayMonthEl = document.querySelector(`.months_${displayMonth}`);
displayMonthEl.style.display = 'block';
}
}
}
在monthChange函数中,传入event,用event.target来更好判断是哪个按钮在点击,向前一个月,则需要先让当前月份隐藏,让displayMonth-- 变成前一个月并选择前一个月的节点显示,如果displayMonth<0了就说明需要往前一年,这时就需要year - 1并重新渲染日历了,需要重新调用insertDate(year),还要将应显示的月份设置为最后一个月。往后一个月的操作是同样的思路。
function containerMove() {
calendarContainer.style.left = '-332px';
calenarHiddenBtn.style.display = 'block';
}
function containerBack(){
calendarContainer.style.left = '20px';
calenarHiddenBtn.style.display = 'none';
}
这两个函数是一个简单的移动动画
if (year === currentYear) {
const currentMonthEl = document.querySelector(`.months_${currentMonth}`);
const currentDateEl = currentMonthEl.querySelector(`.date_${currentDate}`);
currentDateEl.classList.add('active');
}
最后,获取你今天的日期,用以标注当日日期。
// 启动
insertDate(year);
calendarAnimation();
总结
在这个项目中,让我对时间的操作有了很多的实践,getFullYear() getMonth() getDate() getDay()。数组的forEach map,后者会返回一个新的数组,可以用join来将其组合成字符串,在${}中如果内容是一个值会将其值输出。
最后,javascript是面向对象的,我却写得像面向过程,主要还是没有理解对象,不知道该如何选取对象,需要好好去理解一下,或许写一个小游戏的项目会帮助我的理解。

浙公网安备 33010602011771号