0%

Ionic Infinite Calendar

最近有個 Ionic 專案需要把按鍵換頁式的日曆改成垂直滾動式,在這紀錄一下修改過程。

元件規劃

原本的 Calendar Componet 長成下圖的樣子,預設顯示目前月份,可以點擊按鈕,切換上、下個月的視圖。

計畫是把該功能拆分成兩個 Component,最後再組合起來,功能分別如下

  • month-view:顯示某月份日曆
  • scroll-calendar:控制卷軸與動態顯示 month-view

操作情境為 scroll-calendar 顯示目前,與近幾個月的日曆,當卷軸往上或往下時,自動長出後續的 month-view;下圖用 IOS 的日曆來解釋。

展示用的專案架構。

實作開始

步驟(一) month-view 顯示某月份日期

將原本的 calendar 改成 month-view,控制顯示月份的month參數加上@Input裝飾器,使其可以接收到 scroll-calendar 傳入的月份。

1
2
3
4
5
6
7
export class MonthViewComponent implements OnInit {
//略

@Input("month") month: moment.Moment = moment();

//略
}

步驟(二) scroll-calendar 顯示某區間月份

為了顯示目前月份與前後 n 個月,在這裡定義目前為current,n 為buffer,組出這個區間的資料後,用迴圈顯示 month-view。

1
2
3
4
5
6
7
8
9
10
11
12
13
export class ScrollCalendarPage implements OnInit {
constructor() {}

current: moment.Moment = moment();
buffer = 4;
months: Array<moment.Moment> = [];

ngOnInit() {
for (let i = -this.buffer; i <= this.buffer; i++) {
this.months.push(this.current.clone().add(i, "months"));
}
}
}
1
2
3
4
5
6
7
8
9
<ion-header>
<ion-toolbar color="primary">
<ion-title>scroll-calendar</ion-title>
</ion-toolbar>
</ion-header>

<ion-content #content>
<app-month-view *ngFor="let month of months" [month]="month"></app-month-view>
</ion-content>

這時應該要能正確顯示正負四個月的 month-view。

步驟(三) scroll-calendar 設定卷軸初始位置

可以注意到一開始的卷軸會在最上方,而不是當前月份的位置(二月),若要做到比較好的效果,可以在迴圈時添加 month-view 元件的id,並配合 content 的 scrollToPoint,將卷軸移至指定 month-view。

1
2
3
4
5
6
7
<ion-content #content>
<app-month-view
*ngFor="let month of months; let index = index;"
[month]="month"
id="monthView_{{index}}"
></app-month-view>
</ion-content>
1
2
3
4
5
6
7
8
9
10
export class ScrollCalendarPage implements OnInit {
//略

@ViewChild("content", { static: true }) contentElement: IonContent;

ionViewWillEnter() {
let currentMonthView = document.getElementById(`monthView_${this.buffer}`);
this.contentElement.scrollToPoint(0, currentMonthView.offsetTop);
}
}

步驟(四) scroll-calendar 無限滾動卷軸

要做到無限滾動卷軸也很簡單,對迴圈的資料來源months做點變動就行了;這邊監聽卷軸事件,當卷軸往下滾時,若 scrollTop 高於目前月份(month-view)的offsetTop,則對 months 插入最後一筆資料,同時,為了避免頁面內容過於肥大,也刪除第一筆資料;卷軸往上也做相對應的處理。

1
2
3
4
5
6
7
8
<ion-content
#content
scrollEvents="true"
(ionScroll)="onScroll($event)"
(ionScrollEnd)="onScroll($event)"
>
<!-- 略 -->
</ion-content>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export class ScrollCalendarPage implements OnInit {
//略

onScroll = (event: CustomEvent) => {
if (
event.detail.scrollTop <
document.getElementById(`monthView_${this.buffer}`).offsetTop
) {
this.current.subtract(1, "months");
this.months.unshift(this.current.clone().subtract(this.buffer, "months"));
this.months.pop();
return;
}
if (
event.detail.scrollTop >
document.getElementById(`monthView_${this.buffer + 1}`).offsetTop
) {
this.current.add(1, "months");
this.months.splice(0, 1);
this.months.push(this.current.clone().add(this.buffer, "months"));
return;
}
};
}

Demo

可以看到這個頁面永遠只會顯示buffer * 2 + 1個 month-view,一個簡單的垂直滾動日曆就這樣完成了。