Vanilla Calendar:簡潔好看的日曆套件
目錄
- 需求
- 日期區間
- input 點擊
需求
UI 設計完成的畫面希望可以選擇日期區間,並顯示在畫面上:

需要做到的事情包含:
- 可以點選兩個日期就產生日期區間
- 點擊出現日歷,點其他地方或是結束選取時隱藏
基本使用
為了快速地達成需求,完全不考慮手刻,找到了這次的教學套件——Vanilla Calendar。首頁就放了一個可以操作的展示:

基本的使用方法很簡單,將文件上的範例修改一下,就可以套用到 Vue 裡面:
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@uvarov.frontend/[email protected]/build/vanilla-calendar.min.css">
<style>
#calendar{
width: 260px;
}
#picked-date{
margin-top: 10px;
}
</style>
</head>
<body>
<div id="app">
<div id="calendar"></div>
<div id="picked-date">選擇的日期:{{pickedDate}}</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/@uvarov.frontend/[email protected]/build/vanilla-calendar.min.js"></script>
<script>
const app = Vue.createApp({
data() {
return{
pickedDate: ''
}
},
methods: {
initCalendar() {
let that = this;
const calendarDefault = new VanillaCalendar('#calendar', {
type: 'default',
actions: {
clickDay(event, dates) {
that.pickedDate = dates[0];
},
},
});
calendarDefault.init();
},
},
mounted() {
this.initCalendar();
}
});
app.mount('#app')
</script>
</body>
</html>
其中第 42 行的 clickDay() 是套件所提供的 API,讓我們可以在日曆點擊時進行一些處理,這個範例中是將點選到的日期加入 Vue 的 data 中,顯示在畫面上。

需求1. 時間區間
原生日曆最無法支援的功能就是這個,所以我們先來看一下要如何讓我們的日曆可以選擇時間區間:
const app = Vue.createApp({
data() {
return {
pickedDate: ''
}
},
methods: {
initCalendar() {
let that = this;
const calendarDefault = new VanillaCalendar('#calendar', {
// 加上設定
settings: {
selection: {
// 可以選擇區間
day: 'multiple-ranged',
},
},
actions: {
clickDay(event, dates) {
let lastIndex = dates.length - 1;
that.pickedDate = `${dates[0]}~${dates[lastIndex]}`;
},
},
});
calendarDefault.init();
},
},
mounted() {
this.initCalendar();
}
});
app.mount('#app');HTML 的部分不需要改動

從上圖可以看到,現在我們能選取區間了,但因為我是先點選 11/24,才點選 11/01,所以存入的日期區間是反的,因此我們需要做點檢查:
const app = Vue.createApp({
data() {
return {
pickedDate: ''
}
},
methods: {
initCalendar() {
let that = this;
const calendarDefault = new VanillaCalendar('#calendar', {
settings: {
selection: {
day: 'multiple-ranged',
},
},
actions: {
clickDay(event, dates) {
let lastIndex = dates.length - 1;
let firstDate = new Date(dates[0]);
let lastDate = new Date(dates[lastIndex]);
// 如果日期區間的第一個日期 (11/24) 大於最後一個日期 (11/01) 代表是反的
if (firstDate > lastDate) dates.reverse();
that.pickedDate = `${dates[0]}~${dates[lastIndex]}`;
},
},
});
calendarDefault.init();
},
},
mounted() {
this.initCalendar();
}
});
app.mount('#app');
需求2. 樣式修改
第二個需求是希望能修改介面樣式,點擊的時候才出現,選擇完成或是點其他地方就取消。
根據官方文件的範例,我們稍作修改,放到 Vue 裡面:
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@uvarov.frontend/[email protected]/build/vanilla-calendar.min.css">
<style>
/* 設定日曆位置 */
#app {
position: relative;
}
#calendar {
position: absolute;
}
#calendar,
#picked-date,
#popup {
width: 260px;
}
/* 隱藏日曆 */
/* 如果有套用 Bootstrap 可以不需要手動新增 */
.visually-hidden,
.visually-hidden-focusable:not(:focus):not(:focus-within) {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
margin-top: -1px;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
#picked-date {
margin-top: 10px;
}
</style>
</head>
<body>
<div id="app">
<input type="text" readonly id="popup">
<div id="calendar" class="visually-hidden"></div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/@uvarov.frontend/[email protected]/build/vanilla-calendar.min.js"></script>
<script>
const app = Vue.createApp({
methods: {
initCalendar(inputPopupEl, calendarDatepickerPopupEl) {
let that = this;
const calendarDefault = new VanillaCalendar('#calendar', {
settings: {
selection: {
day: 'multiple-ranged',
},
},
actions: {
clickDay(event, dates) {
if (dates.length > 1) {
// 選擇超過一個日期之後隱藏日曆
inputPopupEl.classList.remove('input_focus');
calendarDatepickerPopupEl.classList.add('visually-hidden');
// 和原本的處理方式相同
let lastIndex = dates.length - 1;
let firstDate = new Date(dates[0]);
let lastDate = new Date(dates[lastIndex]);
if (firstDate > lastDate) dates.reverse();
// 把日期區間填回 input 框
inputPopupEl.value = `${dates[0]}~${dates[lastIndex]}`;
}
},
},
});
calendarDefault.init();
},
addToggle(inputPopupEl, calendarDatepickerPopupEl) {
if (!inputPopupEl || !calendarDatepickerPopupEl) return;
const togglePopup = (e) => {
const input = e.target.closest('#popup');
const calendar = e.target.closest('#calendar');
if ((input && !input.classList.contains('input_focus')) || calendar) {
inputPopupEl.classList.add('input_focus');
calendarDatepickerPopupEl.classList.remove('visually-hidden');
} else {
inputPopupEl.classList.remove('input_focus');
calendarDatepickerPopupEl.classList.add('visually-hidden');
}
};
document.addEventListener('click', togglePopup, { capture: true });
},
},
mounted() {
const inputPopupEl = document.querySelector('#popup');
const calendarDatepickerPopupEl = document.querySelector('#calendar');
// 設定 Toggle
this.addToggle(inputPopupEl, calendarDatepickerPopupEl);
// 初始化日曆
this.initCalendar(inputPopupEl, calendarDatepickerPopupEl);
}
});
app.mount('#app');
</script>
</body>
</html>
把 Toggle 和初始化拆開來進行的好處是,如果想要新增一顆清除已選擇區間的按鈕,就可以先把 input 中的值清空後,再次呼叫初始化的方法就好。