最近在作一个相似课程表的需求,须要自制一个日从来支持功能及展示,就顺便研究一下应该怎么开发日历组件。css
// if (total_calendar_list.length > 35) {
// nextNum = 42 - total_calendar_list.length;
// } else {
// nextNum = 35 - total_calendar_list.length;
// }
// if (month === 1 && new Date(year, month, 0).getDay() === 6) {
// nextNum = 0
// }
nextNum = 6 - new Date(year, month+1, 0).getDay()
复制代码
本文主要涉及如下内容:html
层层分离,块块独立vue
在梳理日历逻辑以前我想先记录一下日历样式相关的问题:react
下面是借鉴px2rem模式,写的基于vw为主单位的自适应转化。简单来讲,就是在咱们的设计稿是iPhone8一倍图的状况下,计算出某元素宽度与375(iPhone8最大宽度)的比例再与100vw相乘就获得了,该元素的vw值。由于vw是相对于屏幕的百分比单位,因此就能达到咱们想要的自适应效果啦,不一样的屏幕里,同一元素的展示比例是一致的。git
// 借鉴了Rem布局
@function pxWithVw($n) {
@return 100vw * $n / 375;
}
// 规定极限宽度,避免PC上观感太差
@function pxWithVwMax($n) {
@return 480px * $n / 375;
}
复制代码
有了上面这段SCSS的函数,咱们就基本能够不用考虑屏幕适配的问题了,能够尽情的敲样式啦。关于日历的样式,其实说复杂也还好,咱们只须要在作以前好好的分一下层级就行了。github
如同上图,每个框表示一层元素,最后会有这样的布局--数组
<!--最外层的div限定整个日历的宽度以及一些圆角阴影等样式-->
<div class="calendar">
<!--header则为上图中绿色框的内容,包含上下月切换以及日历title-->
<div class="calendar__header"></div>
<!--顾名思义main则是整个日历的核心内容,也就是日期的展现区域-->
<div class="calendar__main">
<!--星期一~星期日的展现头,列表渲染固定的7个block-->
<div class="main__block-head"></div>
<!--相应月份的日期展现区域,列表渲染-->
<div class="main__block"></div>
</div>
</div>
复制代码
也许你们看完以后比较奇怪calendar__main里面的布局,为何没有把固定的展现头分离开来,当你实际写到这里的时候会发现其实没有这个必要。函数
由于咱们用了pxWithVw去规定calendar__main的宽度以及每一个block的宽度,也就确保了每7块元素一定会占满咱们一行,再利用justify-content: space-around确保咱们每块元素的间隙一致便可。布局
好了,层层分离说完了,什么是块块独立呢?flex
主要指的是日期的展现块,咱们是每块独立的,这样在咱们渲染的时候能够很方便的决定应该以什么样式去展现他,或是应该给他绑定怎么样的事件,给咱们精密控制每一个日期的展现提供了便利。
本小节的内容总结起来其实就一句话--
咱们只须要知道,某个月的1号是星期几,就能把整个日历渲染出来
关于年月日的计算,我这边有两种模式,一种是只计算当月日期,另外一种则是将全年的日期都计算出来。在本篇文章里我想着重记录第一种写法,你们想了解第二种的话能够到个人github里看看这个日历的demo。
咱们先来个看图说话,这个二月份有28天,1号是星期四。那是否是说,咱们只要从周四开始,按顺序渲染出28个'main__block'就行了呢?其实就是这样,关键是怎么把咱们的1号定位到周四,只要这个可以准肯定位到,咱们的日历天然就出来了。
// 定义每月的天数,若是是闰年第二月改成29天
// year=2018;month=1(js--month=0~11)
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
daysInMonth[1] = 29;
}
// 得到指定年月的1号是星期几
let targetDay = new Date(year, month, 1).getDay();
// 将要在calendar__main中渲染的列表
let total_calendar_list = [];
let preNum = targetDay;
// 首先先说一下,咱们的日期是(日--六)这个顺序也就是(0--6)
// 有了上述的前提咱们能够认为targetDay为多少,咱们就只须要在total_calendar_list的数组中push几个content为''的obj做为占位
if (targetDay > 0) {
for (let i = 0; i < preNum; i++) {
let obj = {
type: "pre",
content: ""
};
total_calendar_list.push(obj);
}
}
复制代码
这样一来,1号的位置天然而然就到了咱们须要的星期四了,接下来就只须要按顺序渲染就ok啦。下面是剩下日期数组填充,填充完毕以后return出来供咱们view层使用。
for (let i = 0; i < daysInMonth[month]; i++) {
let obj = {
type: "normal",
content: i + 1
};
total_calendar_list.push(obj);
}
nextNum = 6 - new Date(year, month+1, 0).getDay()
// 与上面的type=pre同理
for (let i = 0; i < nextNum; i++) {
let obj = {
type: "next",
content: ""
};
total_calendar_list.push(obj);
}
return total_calendar_list;
复制代码
data() {
return {
// ...
selectedYear: new Date().getFullYear(),
selectedMonth: new Date().getMonth(),
selectedDate: new Date().getDate()
};
}
handlePreMonth() {
if (this.selectedMonth === 0) {
this.selectedYear = this.selectedYear - 1
this.selectedMonth = 11
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth - 1
this.selectedDate = 1
}
}
handleNextMonth() {
if (this.selectedMonth === 11) {
this.selectedYear = this.selectedYear + 1
this.selectedMonth = 0
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth + 1
this.selectedDate = 1
}
}
复制代码
就是这么简单,须要注意的点是跨年的时间转换,咱们须要在变动月份的同时把年份也改变,这样才能渲染出正确的日期。
也许你们会有疑问,怎么变动了月份或年份以后不须要从新计算一第二天期呢?实际上是有计算的,不知你们是否还记得,vue但是数据驱动变动的,咱们只须要关注数据的变动便可,其余东西vue都会帮咱们解决。
handleDayClick(item) {
if (item.type === 'normal') {
// do anything...
this.selectedDate = Number(item.content)
}
}
复制代码
在渲染列表的时候我就给每个block绑定了click事件,这样作的好处就是调用十分方便,点击每个block的时候,能够获取该block的内容而后do anything you like
固然咱们也能够给外层的父级元素绑定事件监听,经过事件流来解决每一个block的点击事件,这里看我的习惯~毕竟元素数量不是特别多
一个移动端日历貌似也有惊无险的完成啦,整体来讲日历这活仍是偏样式方面的,对逻辑的要求不是特别高,对样式的要求却是挺高的须要对flexbox布局有必定理解,才能迅速的吧日历的骨架搭起来,虽然也不必定说必须用flex,不过我的认为用flex的效率会稍高一些。
啰嗦一下,为何想起来写日历?固然是业务需求啦,因此说这个日历组件一开始是react写的,后面想在vue里也尝试一下就改为了vue。其实在react里面写也是大同小异啦,只不过我会把日期的block抽离成无状态组件,也不为啥就感受比较好看:)