用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是面向对象的,我却写得像面向过程,主要还是没有理解对象,不知道该如何选取对象,需要好好去理解一下,或许写一个小游戏的项目会帮助我的理解。

posted @ 2020-08-17 15:48  ··十方··  阅读(720)  评论(0)    收藏  举报