介绍
用原生三件套从零到一实现以下日历模块,样式如图
目前具备以下功能
- 展示当前年月日,并对当前日其展示激活状态
- 点击
上一月
下一月
进行跳转
完成骨架搭建
首先先对,整体样式进行构思(对于毫无艺术细胞的我来说,差点要了半条命),最后决定先按照上图先做着,后边要改再说,日期内容采用的是表格
进行渲染
<div id="calendar">
<div class="head">
<div class="pre-month">上一月</div>
<div class="date">
<!-- 这里先用的是死数据 -->
<div class="year">2022</div>
<div class="month">10月</div>
</div>
<div class="next-month">下一月</div>
</div>
<div class="main">
<table>
<thead>
<tr>
<!-- 表格关于周几的就直接写上不生成了 -->
<td>日</td>
<td>一</td>
<td>二</td>
<td>三</td>
<td>四</td>
<td>五</td>
<td>六</td>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
添加样式
样式就按照上面那个不太好看的样式来就行,因为是表格,所以布局的内容比较少
#calendar {
display: flex;
flex-direction: column;
width: 100vw;
box-sizing: border-box;
padding: 10px;
margin: 26px auto;
background-color: #f49bc4;
}
/* 标题部分 */
#calendar .head {
display: flex;
width: 100%;
margin-top: 10px;
justify-content: space-evenly;
font-size: 22px;
font-weight: 700;
color: #50508a90;
}
#calendar .head .pre-month,
#calendar .head .next-month,
#calendar .head .pre-month .year,
#calendar .head .month {
cursor: pointer;
}
/* 日期部分 */
#calendar .main {
width: 100%;
color: aliceblue;
font-size: 20px;
font-weight: 900;
margin-top: 20px;
text-align: center;
}
#calendar .main table {
width: 100%;
color: #50508a90;
}
#calendar .main table tbody tr {
width: 100%;
}
#calendar .main table tbody tr td {
height: 36px;
line-height: 36px;
color: #fff;
}
.active {
background-color: #50508a90;
}
日期部分
这个练习案例,最主要的就是对于js
内建对象Date
的使用了
当前日期
首先我们需要拿到当前日期,然后一层一层的拿到以下内容:
- 今天星期几
- 当前月份有多少天
- 这个月的第一天是星期几
获取当前日期是比较简单的
const date = new Date()
const year = date.getFullYear() // 年份
const month = date.getMonth() + 1 // 月份
const weekDay = date.getDay() // 周几
const day = date.getDate() // 日期
后面在多个位置都需要根据日期获取这些数据,所以我把他抽成了一个函数,通过外界传入的日期对象
获取对应的相关数据
function getDate(date) {
const year = date.getFullYear() // 年份
const month = date.getMonth() + 1 // 月份
const weekDay = date.getDay() // 周几
const day = date.getDate() // 日期
return {
year,
month,
weekDay,
day,
}
}
注意:
Date
对象的getMonth()
获取到的月数是从零开始的
获取本月的天数
获取当前月数的方法,我想半天没想到,后来查MDN
(还是对原生Date
对象的用法不太熟悉),找到了setDate
方法,该方法根据本地时间来指定一个日期对象的天数,如果当我们传入的天数超出了该月的合理范围,它会根据一定规则进行更新对象:
原时间对象会被改变
- 传入 0 :设置为上一个月的最后一天
- -1 :设置为上一个月的倒数第二天
- 32 :超出了当月的范围,往下一个月类推
根据上面的特点,所以我们可有下面的方法来获取某个月的天数
function getMonthDays(date) {
// 将日期设置为32,表示自动计算为下个月的第几天(这取决于当前月份有多少天)
let oldDate = new Date()
date.setDate(32)
let days = 32 - date.getDate()
let dayArrs = []
let i = 1
while (i <= days) {
dayArrs.push(oldDate.setDate(i))
i++
}
// 返回当前月份的天数
return {
days,
dayArrs,
}
}
在这个方法中,不仅获取到了天数,也将每个日期对应的时间戳返回了
获取第一天的星期数
对于这个功能没有单独抽取方法,大概思路就是,使用setDate
方法,将日期设置为1,然后getDay
// 这里使用 copyDate 是因为 setDate会改变源对象,影响了后面的操作,开始找半天没找到哪里不对
let firstDay = new Date(copyDate.setDate(1)).getDay()
收尾工作
编写一个renderDate
函数用于渲染操作,同时也方便在其他方法中调用(如事件,初始化操作),这里用了表格,所以就根据日期的特点,采用的是6 x 7
的表格,所以里边套了两层循环,在循环中获取了哪一个日期是当前活跃的
function renderDate(date) {
const copyDate = new Date(date) // 拷贝一份,防止后面一不小心改了
tbody.innerHTML = "" // 清空之前的内容 T
const { year, month, day } = getDate(date)
// 获取当前的年月日
const { dayArrs, days } = getCountDays(date)
// 获取本月所有的时间戳, 以及本月的天数
//#region
currentMonth = month // 记录状态
currentYear = year // 记录状态
//#endregion
//#region 头部展示部分
yearEl.innerHTML = year
monthEl.innerHTML = month + "月"
//#endregion
// 用于统计当前已经渲染上的格子数
let count = 0
// 本月的第一天的星期数
let firstDay = new Date(copyDate.setDate(1)).getDay()
// 用于暂存创建的 tr 元素
let trs = document.createDocumentFragment()
for (let i = 0; i < 6; i++) {
// 第一层循环代表行数
let tr = document.createElement("tr")
// 用于暂存创建的 td 元素
let tds = document.createDocumentFragment()
for (let j = 0; j < 7; j++) {
// 这一层表示列数
let td = document.createElement("td")
// 当前日期的星期数
let day = new Date(dayArrs[count]).getDay()
if (i === 0 && j < firstDay) {
// 这里的判断用于跳过 1 号之前的日期格子
tds.append(td)
continue
}
if (count < days) {
// 如果以渲染的格子数小于天数就继续添加
if (new Date(dayArrs[count]).getDate() === new Date().getDate()) {
// 将当前日期设置为激活状态
td.classList.add("active")
}
td.innerHTML = new Date(dayArrs[count]).getDate()
}
tds.append(td)
count++
}
tr.append(tds)
trs.append(tr)
}
tbody.append(trs)
}
完成该函数之后,我们通过一个IIFE
进行初始化操作
(function () {
let date = new Date() // 获取当前日期
renderDate(date)
})()
结束语
到这里,具有基本功能的日历差不多就实现了,虽然功能比较简单,但是我还是花了好长时间完成,不过也算是对Date
熟悉了些了,这波不亏!后续完成更加复杂的功能