基于Vue开发一个日历组件
最近在做一個(gè)類似課程表的需求,需要自制一個(gè)日歷來(lái)支持功能及展現(xiàn),就順便研究一下應(yīng)該怎么開(kāi)發(fā)日歷組件。
更新
- 2.23修復(fù)了2026年2月份會(huì)渲染多一行的bug,謝謝@深藍(lán)一人童鞋提出的bug,解決方案是給二月份的日歷做特殊處理,new Date(year, month 1, 0).getDay() === 6時(shí)不會(huì)再渲染后面的日期。
- 下班更新一哈,更科學(xué)的邏輯// 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()
本文主要涉及以下內(nèi)容:
- 怎么開(kāi)發(fā)一套日歷皮膚?
- 怎么計(jì)算年月日?
- 怎么開(kāi)發(fā)日歷相關(guān)的功能?
- 總結(jié)&DEMO源碼
怎么開(kāi)發(fā)一套日歷皮膚?
層層分離,塊塊獨(dú)立
在梳理日歷邏輯之前我想先記錄一下日歷樣式相關(guān)的問(wèn)題:
下面是借鑒px2rem模式,寫(xiě)的基于vw為主單位的自適應(yīng)轉(zhuǎn)化。簡(jiǎn)單來(lái)說(shuō),就是在我們的設(shè)計(jì)稿是iPhone8一倍圖的情況下,計(jì)算出某元素寬度與375(iPhone8最大寬度)的比例再與100vw相乘就得到了,該元素的vw值。因?yàn)関w是相對(duì)于屏幕的百分比單位,所以就能達(dá)到我們想要的自適應(yīng)效果啦,不同的屏幕里,同一元素的展現(xiàn)比例是一致的。
// 借鑒了Rem布局 @function pxWithVw($n) {@return 100vw * $n / 375; } // 規(guī)定極限寬度,避免PC上觀感太差 @function pxWithVwMax($n) {@return 480px * $n / 375; }有了上面這段SCSS的函數(shù),我們就基本可以不用考慮屏幕適配的問(wèn)題了,可以盡情的敲樣式啦。關(guān)于日歷的樣式,其實(shí)說(shuō)復(fù)雜也還好,我們只需要在做之前好好的分一下層級(jí)就好了。
如同上圖,每一個(gè)框表示一層元素,最后會(huì)有這樣的布局--
<!--最外層的div限定整個(gè)日歷的寬度以及一些圓角陰影等樣式--> <div class="calendar"><!--header則為上圖中綠色框的內(nèi)容,包含上下月切換以及日歷title--><div class="calendar__header"></div><!--顧名思義main則是整個(gè)日歷的核心內(nèi)容,也就是日期的展示區(qū)域--><div class="calendar__main"><!--星期一~星期日的展示頭,列表渲染固定的7個(gè)block--><div class="main__block-head"></div><!--相應(yīng)月份的日期展示區(qū)域,列表渲染--><div class="main__block"></div></div> </div>也許大家看完之后比較奇怪calendar__main里面的布局,為什么沒(méi)有把固定的展示頭分離開(kāi)來(lái),當(dāng)你實(shí)際寫(xiě)到這里的時(shí)候會(huì)發(fā)現(xiàn)其實(shí)沒(méi)有這個(gè)必要。
因?yàn)槲覀冇昧藀xWithVw去規(guī)定calendar__main的寬度以及每個(gè)block的寬度,也就確保了每7塊元素必定會(huì)占滿我們一行,再利用justify-content: space-around確保我們每塊元素的間隙一致即可。
好了,層層分離說(shuō)完了,什么是塊塊獨(dú)立呢?
主要指的是日期的展示塊,我們是每塊獨(dú)立的,這樣在我們渲染的時(shí)候可以很方便的決定應(yīng)該以什么樣式去展示他,或是應(yīng)該給他綁定怎么樣的事件,給我們精密控制每個(gè)日期的展示提供了便利。
怎么計(jì)算年月日?
本小節(jié)的內(nèi)容總結(jié)起來(lái)其實(shí)就一句話--
我們只需要知道,某個(gè)月的1號(hào)是星期幾,就能把整個(gè)日歷渲染出來(lái)
關(guān)于年月日的計(jì)算,我這邊有兩種模式,一種是只計(jì)算當(dāng)月日期,另一種則是將整年的日期都計(jì)算出來(lái)。在本篇文章里我想著重記錄第一種寫(xiě)法,大家想了解第二種的話可以到我的github里看看這個(gè)日歷的demo。
我們先來(lái)個(gè)看圖說(shuō)話,這個(gè)二月份有28天,1號(hào)是星期四。那是不是說(shuō),我們只要從周四開(kāi)始,按順序渲染出28個(gè)'main__block'就好了呢?其實(shí)就是這樣,關(guān)鍵是怎么把我們的1號(hào)定位到周四,只要這個(gè)能夠準(zhǔn)確定位到,我們的日歷自然就出來(lái)了。
// 定義每個(gè)月的天數(shù),如果是閏年第二月改為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號(hào)是星期幾 let targetDay = new Date(year, month, 1).getDay(); // 將要在calendar__main中渲染的列表 let total_calendar_list = []; let preNum = targetDay; // 首先先說(shuō)一下,我們的日期是(日--六)這個(gè)順序也就是(0--6) // 有了上述的前提我們可以認(rèn)為targetDay為多少,我們就只需要在total_calendar_list的數(shù)組中push幾個(gè)content為''的obj作為占位 if (targetDay > 0) {for (let i = 0; i < preNum; i ) {let obj = {type: "pre",content: ""};total_calendar_list.push(obj);} }這樣一來(lái),1號(hào)的位置自然而然就到了我們需要的星期四了,接下來(lái)就只需要按順序渲染就ok啦。下面是剩下日期數(shù)組填充,填充完畢之后return出來(lái)供我們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;怎么開(kāi)發(fā)日歷相關(guān)的功能?
如何選擇上一個(gè)月或下一個(gè)月?
data() {return {// ...selectedYear: new Date().getFullYear(),selectedMonth: new Date().getMonth(),selectedDate: new Date().getDate()}; }handlePreMonth() {if (this.selectedMonth === 0) {this.selectedYear = this.selectedYear - 1this.selectedMonth = 11this.selectedDate = 1} else {this.selectedMonth = this.selectedMonth - 1this.selectedDate = 1} }handleNextMonth() {if (this.selectedMonth === 11) {this.selectedYear = this.selectedYear 1this.selectedMonth = 0this.selectedDate = 1} else {this.selectedMonth = this.selectedMonth 1this.selectedDate = 1} }就是這么簡(jiǎn)單,需要注意的點(diǎn)是跨年的時(shí)間轉(zhuǎn)換,我們需要在變更月份的同時(shí)把年份也改變,這樣才能渲染出正確的日期。
也許大家會(huì)有疑問(wèn),怎么變更了月份或年份之后不需要重新計(jì)算一次日期呢?其實(shí)是有計(jì)算的,不知大家是否還記得,vue可是數(shù)據(jù)驅(qū)動(dòng)變更的,我們只需要關(guān)注數(shù)據(jù)的變更即可,其他東西vue都會(huì)幫我們解決。
如果選中某一天?
handleDayClick(item) {if (item.type === 'normal') {// do anything...this.selectedDate = Number(item.content)} }在渲染列表的時(shí)候我就給每一個(gè)block綁定了click事件,這樣做的好處就是調(diào)用十分方便,點(diǎn)擊每一個(gè)block的時(shí)候,可以獲取該block的內(nèi)容然后do anything you like
當(dāng)然我們也可以給外層的父級(jí)元素綁定事件監(jiān)聽(tīng),通過(guò)事件流來(lái)解決每個(gè)block的點(diǎn)擊事件,這里看個(gè)人習(xí)慣~畢竟元素?cái)?shù)量不是特別多
總結(jié)
一個(gè)移動(dòng)端日歷貌似也有驚無(wú)險(xiǎn)的完成啦,總體來(lái)說(shuō)日歷這活還是偏樣式方面的,對(duì)邏輯的要求不是特別高,對(duì)樣式的要求倒是挺高的需要對(duì)flexbox布局有一定理解,才能迅速的吧日歷的骨架搭起來(lái),雖然也不一定說(shuō)必須用flex,不過(guò)個(gè)人認(rèn)為用flex的效率會(huì)稍高一些。
基于Vue寫(xiě)的日歷DEMO--Github
啰嗦一下,為什么想起來(lái)寫(xiě)日歷?當(dāng)然是業(yè)務(wù)需求啦,所以說(shuō)這個(gè)日歷組件一開(kāi)始是react寫(xiě)的,后面想在vue里也嘗試一下就改成了vue。其實(shí)在react里面寫(xiě)也是大同小異啦,只不過(guò)我會(huì)把日期的block抽離成無(wú)狀態(tài)組件,也不為啥就感覺(jué)比較好看:)
總結(jié)
以上是生活随笔為你收集整理的基于Vue开发一个日历组件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 基于canvas的骨骼动画
- 下一篇: 为什么Vue不能观察到数组length的