基於Vue開發一個日曆組件

最近在作一個相似課程表的需求,須要自制一個日從來支持功能及展示,就順便研究一下應該怎麼開發日曆組件。css

更新

  • 2.23修復了2026年2月份會渲染多一行的bug,謝謝@深藍一人童鞋提出的bug,解決方案是給二月份的日曆作特殊處理,new Date(year, month+1, 0).getDay() === 6時不會再渲染後面的日期。
  • 下班更新一哈,更科學的邏輯
    // 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

  • 怎麼開發一套日曆皮膚?
  • 怎麼計算年月日?
  • 怎麼開發日曆相關的功能?
  • 總結&DEMO源碼

怎麼開發一套日曆皮膚?

層層分離,塊塊獨立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的效率會稍高一些。

基於Vue寫的日曆DEMO--Github

囉嗦一下,爲何想起來寫日曆?固然是業務需求啦,因此說這個日曆組件一開始是react寫的,後面想在vue裏也嘗試一下就改爲了vue。其實在react裏面寫也是大同小異啦,只不過我會把日期的block抽離成無狀態組件,也不爲啥就感受比較好看:)

相關文章
相關標籤/搜索