Fullcalendar 全方位教程
目录
-
(图片来源网络,侵删)- 什么是 FullCalendar?
- 为什么选择 FullCalendar?
- 环境准备:安装与引入
- 创建你的第一个日历(Hello World)
-
- 初始化选项
- 、视图和按钮
- 事件的基本使用
- 事件对象详解
-
- 事件操作:点击、拖拽、调整大小
- 使用外部数据源(AJAX)
- 事件渲染函数:
eventContent
-
- 自定义视图
- 日期点击与范围选择
- 插件系统
- 国际化
- 主题与样式定制
-
(图片来源网络,侵删)- 需求分析
- 项目结构
- 实现步骤
- 完整代码示例
-
- 常见问题解答
- 性能优化建议
第一部分:入门指南
什么是 FullCalendar?
FullCalendar 是一个功能强大、高度可定制的 JavaScript 日历库,它可以轻松地在你的网页中嵌入交互式的事件日历,它支持多种视图(月、周、日、时间线等),并内置了丰富的事件处理功能。
为什么选择 FullCalendar?
- 功能全面:支持拖拽、调整大小、点击、外部数据源等。
- 文档完善:官方文档非常详尽,社区活跃。
- 主题丰富:提供多个官方主题(如 Bootstrap 5, Material, Tailwind CSS),并支持自定义样式。
- 视图多样:除了标准的月/周/日视图,还支持时间线视图(Timeline)等高级视图。
- 商业支持:提供免费版和功能更强大的付费商业版。
环境准备:安装与引入
FullCalendar 提供了多种安装方式,这里介绍最常用的两种。
通过 CDN 引入(最简单,适合快速上手)
直接在 HTML 文件中引入 CSS 和 JavaScript 文件,这种方式无需构建工具。
<!-- 1. 引入 FullCalendar 的 CSS --> <link href='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css' rel='stylesheet' /> <!-- 2. 引入 FullCalendar 的 JS --> <script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script>
通过 npm/yarn 安装(推荐用于生产环境)
如果你使用的是现代前端框架(如 React, Vue, Angular)或构建工具(如 Vite, Webpack),推荐使用 npm 或 yarn 安装。
# 使用 npm npm install fullcalendar --save # 使用 yarn yarn add fullcalendar
安装后,你需要根据你使用的框架来导入相应的模块。
创建你的第一个日历
我们使用最简单的 CDN 方式来创建一个基础日历。
步骤:
- 创建一个 HTML 文件(
index.html)。 - 在
<body>中创建一个<div>作为日历的容器,并给它一个id。 - 在
<body>的底部,编写 JavaScript 代码来初始化日历。
index.html 代码:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />FullCalendar 示例</title>
<!-- 1. 引入 FullCalendar 的 CSS -->
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css' rel='stylesheet' />
<style>
/* 给日历容器设置一个高度,否则日历将不可见 */
#calendar-container {
max-width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<!-- 2. 创建日历容器 -->
<div id='calendar-container'></div>
<!-- 3. 引入 FullCalendar 的 JS 并初始化 -->
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const calendarEl = document.getElementById('calendar-container'); // 获取容器元素
// 初始化日历
const calendar = new FullCalendar.Calendar(calendarEl, {
// 初始化选项
initialView: 'dayGridMonth', // 初始视图为月视图
locale: 'zh-cn' // 设置语言为中文
});
calendar.render(); // 渲染日历
});
</script>
</body>
</html>
解释:
DOMContentLoaded:确保 DOM 完全加载后再执行 JavaScript 代码。document.getElementById('calendar-container'):获取我们创建的日历容器。new FullCalendar.Calendar(calendarEl, { ... }):这是 FullCalendar 的核心构造函数,它接收两个参数:容器元素和配置对象。initialView: 'dayGridMonth':设置日历的初始显示模式为“月视图”。locale: 'zh-cn':设置日历的语言为简体中文,你需要引入相应的语言包(CDN 方式会自动处理)。calendar.render():执行此方法后,日历才会被真正渲染到页面上。
用浏览器打开这个 index.html 文件,你就能看到一个简单的中文月历了!
第二部分:核心配置与基本功能
初始化选项
初始化选项是一个 JavaScript 对象,用于配置日历的方方面面。
const calendar = new FullCalendar.Calendar(calendarEl, {
// ... 其他选项
initialView: 'dayGridWeek', // 初始视图为周视图
headerToolbar: { // 自定义顶部工具栏
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
height: 'auto', // 设置日历高度
editable: true, // 允许事件拖拽和调整大小
selectable: true, // 允许选择日期范围
nowIndicator: true // 显示当前时间指示线
});
、视图和按钮
通过 headerToolbar 选项可以高度自定义顶部导航栏。
headerToolbar: {
left: 'prev,next today myCustomButton', // 左侧按钮
center: 'title', // 中间显示当前月份/周/日
right: 'dayGridMonth,timeGridWeek,timeGridDay' // 右侧视图切换按钮
}
prev,next,today:内置的上一页、下一页、今天按钮。:显示当前视图的标题(如 "2025年10月")。dayGridMonth,timeGridWeek,timeGridDay:视图切换按钮,按钮文本与视图名称相同。- 你也可以自定义按钮,
myCustomButton,然后通过customButtons选项来定义它的行为。
事件的基本使用
事件是日历的核心数据,最简单的方式是在初始化时直接提供一个事件数组。
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
events: [ // 事件数组
{
title: '团队会议',
start: '2025-10-10T10:00:00',
end: '2025-10-10T12:00:00'
},
{
title: '项目截止日期',
start: '2025-10-15',
color: 'red' // 设置事件背景色
},
{
title: '午餐约会',
start: '2025-10-12',
allDay: false // 表示这是一个全天事件(默认为 true)
}
]
});
事件对象详解
一个事件对象可以包含很多属性,以下是常用属性:
| 属性 | 类型 | 描述 |
| :--- | :--- | :--- | | string | 事件的标题(显示在日历上)。 |
| start | Date 或 string | 事件的开始时间,可以是 Date 对象或 ISO8601 格式的字符串(如 '2025-10-10T10:00:00')。 |
| end | Date 或 string | 事件的结束时间,如果未提供,则默认与 start 相同。 |
| allDay | boolean | 是否为全天事件,默认为 true,如果为 false,则会显示在时间视图中。 |
| color / backgroundColor | string | 事件的背景颜色,可以是颜色名('red')、十六进制('#ff0000')或 RGB。 |
| textColor | string | 事件文本的颜色。 |
| url | string | 点击事件时跳转的链接。 |
| editable | boolean | 是否可以拖拽或调整此事件的大小,默认为 true(如果日历的 editable 为 true)。 |
| extendedProps | object | 一个自定义对象,可以存放任何你需要的额外数据。 |
第三部分:事件交互与数据源
事件操作:点击、拖拽、调整大小
要启用这些交互功能,只需在初始化选项中设置相应的标志。
const calendar = new FullCalendar.Calendar(calendarEl, {
editable: true, // 允许事件拖拽和调整大小
selectable: true, // 允许在日历上拖动鼠标来选择日期范围
selectMirror: true, // 选择时显示一个镜像事件
dayMaxEvents: true, // 当某天的事件过多时,显示 "+x more" 按钮
// ... 其他选项
});
监听事件:
FullCalendar 提供了丰富的事件回调函数,让你可以在用户操作时执行自定义逻辑。
const calendar = new FullCalendar.Calendar(calendarEl, {
// ... 其他选项
dateClick: function(info) { // 监听日期点击
alert('点击的日期是: ' + info.dateStr);
console.log(info); // info 对象包含丰富的信息,如点击的日期、日期对象、JS 事件等
},
eventClick: function(info) { // 监听事件点击
if (confirm(`你确定要删除事件 "${info.event.title}" 吗?`)) {
info.event.remove(); // 删除事件
}
},
eventDrop: function(info) { // 监听事件拖拽
alert(`事件 "${info.event.title}" 被移动到了 ${info.event.start.toISOString()}`);
// 在这里发送 AJAX 请求到后端,更新数据库
},
eventResize: function(info) { // 监听事件调整大小
alert(`事件 "${info.event.title}" 的时间范围被调整为 ${info.event.start.toISOString()} - ${info.event.end.toISOString()}`);
// 在这里发送 AJAX 请求到后端,更新数据库
}
});
使用外部数据源
在实际应用中,事件数据通常来自服务器,FullCalendar 通过 events 选项可以轻松实现。
events 选项可以接受一个 URL 字符串,FullCalendar 会自动向该 URL 发送 GET 请求来获取事件数据。
后端 API 示例 (Node.js/Express):
// server.js
const express = require('express');
const app = express();
const port = 3000;
app.get('/api/events', (req, res) => {
// 模拟从数据库获取数据
const events = [
{ id: 1, title: '远程会议', start: '2025-10-11T09:00:00', end: '2025-10-11T10:00:00' },
{ id: 2, title: '代码审查', start: '2025-10-12T14:00:00', end: '2025-10-12T15:30:00' },
];
res.json(events);
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
前端代码:
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridWeek',
events: '/api/events' // 直接指向你的 API 端点
});
数据格式要求:
FullCalendar 期望从 API 获取的是一个 JSON 数组,数组中的每个对象都遵循我们之前提到的事件对象格式。
事件渲染函数:eventContent
如果你想对事件进行更复杂的自定义渲染,而不仅仅是改变颜色,可以使用 eventContent 函数。
const calendar = new FullCalendar.Calendar(calendarEl, {
// ... 其他选项
eventContent: function(arg) { // arg 是事件对象
let eventTitle = arg.event.title;
// 根据事件标题返回不同的内容
if (eventTitle.includes('重要')) {
return {
html: `<div class="important-event">
<i class="fas fa-exclamation-circle"></i>
<b>${eventTitle}</b>
</div>`
};
} else {
return {
html: `<div class="normal-event">${eventTitle}</div>`
};
}
}
});
你需要在 HTML 中引入 Font Awesome 等图标库来使用 i 标签。eventContent 函数必须返回一个包含 html 或 domNodes 属性的对象。
第四部分:高级功能与自定义
自定义视图
除了内置的视图,你还可以组合视图来创建自定义视图,创建一个左侧为月视图,右侧为周视图的布局。
import { Calendar } from '@fullcalendar/react'; // 如果你使用 React
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction'; // 包含拖拽等交互功能
// 在初始化时传入插件
const calendar = new FullCalendar.Calendar(calendarEl, {
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
initialView: 'dayGridMonth', // 默认视图
views: {
customTwoDayView: { // 自定义视图名称
type: 'dayGrid', // 基于哪个视图类型
duration: { days: 2 } // 显示两天
},
customWeekSideBar: { // 一个更复杂的例子
type: 'dayGridMonth', // 基于月视图
sideContent: { // 在日历右侧添加内容
html: '<div>这里是侧边栏内容</div>'
},
sideContentOverlap: false // 确保侧边栏不覆盖日历
}
}
});
日期点击与范围选择
selectAllow: 一个函数,用于判断用户是否可以选择某个日期范围。select: 当用户选择日期范围后触发的回调。
const calendar = new FullCalendar.Calendar(calendarEl, {
selectable: true,
selectMirror: true,
select: function(info) { // info 包含 start, end, allDay 等
const title = prompt('请输入新事件的标题:');
if (title) {
calendar.addEvent({ title: title, start: info.start, end: info.end, allDay: info.allDay });
}
calendar.unselect(); // 清除选择状态
},
selectAllow: function(selectInfo) {
// 不允许选择过去的日期
return selectInfo.start >= new Date();
}
});
插件系统
FullCalendar 的许多高级功能(如时间线视图、Google Calendar 集成)都是以插件形式存在的,使用插件前必须先安装并导入。
示例:使用 @fullcalendar/timegrid 插件
npm install @fullcalendar/timegrid
import { Calendar } from '@fullcalendar/react';
import timeGridPlugin from '@fullcalendar/timegrid';
import dayGridPlugin from '@fullcalendar/daygrid';
// 在组件或初始化代码中
<Calendar
plugins={[ dayGridPlugin, timeGridPlugin ]}
initialView="timeGridWeek"
/>
国际化
FullCalendar 支持多种语言,你需要引入对应的语言包。
CDN 方式:
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/locales/zh-cn.global.min.js'></script>
npm 方式:
import { zh-cn } from '@fullcalendar/locales';
// ...
new FullCalendar.Calendar(calendarEl, {
locale: zh-cn, // 直接导入的语言对象
// ...
});
主题与样式定制
FullCalendar 提供了开箱即用的主题,可以让你轻松匹配 UI 框架。
- Bootstrap 5 主题:
@fullcalendar/bootstrap5 - Material (MUI) 主题:
@fullcalendar/material - Tailwind CSS 主题:
@fullcalendar/tailwind
示例:使用 Tailwind CSS 主题
npm install @fullcalendar/tailwind
import { Calendar } from '@fullcalendar/react';
import tailwindTheme from '@fullcalendar/tailwind';
import dayGridPlugin from '@fullcalendar/daygrid';
// ...
<Calendar
plugins={[ dayGridPlugin, tailwindTheme ]}
initialView="dayGridMonth"
/>
你还需要确保你的项目已经正确配置了 Tailwind CSS。
第五部分:实战案例:构建一个任务管理系统
我们将创建一个简单的任务管理系统,用户可以在日历上添加、查看和删除任务。
需求:
- 初始显示月视图。
- 点击任意一天,弹窗输入任务标题并添加。
- 任务以不同颜色显示。
- 点击任务可以删除它。
- 任务数据存储在浏览器的
localStorage中。
实现步骤:
-
HTML 结构 (
index.html):<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>FullCalendar 任务管理</title> <link href='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css' rel='stylesheet' /> <style> body { font-family: Arial, sans-serif; } #calendar { max-width: 900px; margin: 40px auto; } .modal { display: none; position: fixed; z-index: 1; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.4); } .modal-content { background-color: #fefefe; margin: 15% auto; padding: 20px; border: 1px solid #888; width: 80%; max-width: 300px; } </style> </head> <body> <div id='calendar'></div> <!-- 添加任务的模态框 --> <div id="taskModal" class="modal"> <div class="modal-content"> <h2>添加新任务</h2> <input type="text" id="taskTitleInput" placeholder="输入任务标题"> <button id="addTaskBtn">添加</button> <button id="cancelBtn">取消</button> </div> </div> <script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script> <script src="app.js"></script> </body> </html> -
JavaScript 逻辑 (
app.js):document.addEventListener('DOMContentLoaded', function() { const calendarEl = document.getElementById('calendar'); const modal = document.getElementById('taskModal'); const taskInput = document.getElementById('taskTitleInput'); const addTaskBtn = document.getElementById('addTaskBtn'); const cancelBtn = document.getElementById('cancelBtn'); let selectedDate = null; // 从 localStorage 加载事件 function loadEvents() { const storedEvents = localStorage.getItem('calendarEvents'); return storedEvents ? JSON.parse(storedEvents) : []; } // 保存事件到 localStorage function saveEvents(events) { localStorage.setItem('calendarEvents', JSON.stringify(events)); } const calendar = new FullCalendar.Calendar(calendarEl, { initialView: 'dayGridMonth', locale: 'zh-cn', editable: true, selectable: true, events: loadEvents(), // 初始加载事件 // 点击日期时,打开模态框 dateClick: function(info) { selectedDate = info.dateStr; modal.style.display = 'block'; taskInput.value = ''; taskInput.focus(); }, // 点击事件时,删除任务 eventClick: function(info) { if (confirm(`确定要删除任务 "${info.event.title}" 吗?`)) { const events = calendar.getEvents(); const eventToRemove = events.find(e => e.id === info.event.id); if (eventToRemove) { eventToRemove.remove(); saveEvents(calendar.getEvents().map(e => ({ id: e.id, title: e.title, start: e.start.toISOString().split('T')[0], color: e.backgroundColor }))); } } }, // 事件被拖拽或调整大小后,更新 localStorage eventChange: function(info) { saveEvents(calendar.getEvents().map(e => ({ id: e.id, title: e.title, start: e.start.toISOString().split('T')[0], color: e.backgroundColor }))); } }); calendar.render(); // 添加任务按钮点击事件 addTaskBtn.addEventListener('click', function() { const title = taskInput.value.trim(); if (title) { calendar.addEvent({ id: String(Date.now()), // 简单生成唯一 ID title: title, start: selectedDate, color: '#' + Math.floor(Math.random()*16777215).toString(16) // 随机颜色 }); saveEvents(calendar.getEvents().map(e => ({ id: e.id, title: e.title, start: e.start.toISOString().split('T')[0], color: e.backgroundColor }))); modal.style.display = 'none'; } }); // 取消按钮点击事件 cancelBtn.addEventListener('click', function() { modal.style.display = 'none'; }); // 点击模态框外部关闭 window.addEventListener('click', function(event) { if (event.target == modal) { modal.style.display = 'none'; } }); });
这个案例涵盖了日历的核心交互,并与本地存储结合,形成了一个可用的迷你应用。
第六部分:常见问题与最佳实践
常见问题解答
Q: 为什么我的日历显示不出来?
A: 最常见的原因是没有给容器设置高度,日历需要一个明确的高度才能渲染,请确保你的 CSS 中 #calendar-container 有 height 或 max-height 属性。
Q: 如何在 React/Vue 中使用 FullCalendar?
A: FullCalendar 官方为这些框架提供了专门的封装包(@fullcalendar/react, @fullcalendar/vue),使用它们可以更方便地管理日历的生命周期和状态,请查阅官方文档获取相应框架的集成指南。
Q: 如何处理海量事件数据? A: 如果事件数量非常大(例如超过 1000 条),一次性渲染所有事件会导致性能问题,解决方案是:
- 分页/懒加载:根据当前视图的范围(如当前月)向后端请求该范围内的事件。
- 使用
eventSources:将events替换为eventSources,它是一个数组,可以包含多个数据源,每个数据源都可以是一个返回 Promise 的函数,实现按需加载。
Q: 如何实现事件的“拖拽到外部”?
A: 这是一个高级功能,你需要监听 eventDragStop 事件,然后判断事件的 DOM 元素是否已经不在日历容器内,如果是,则执行删除或移动操作。
性能优化建议
- 按需加载视图和插件:只引入你需要的视图(如
dayGridPlugin)和插件,不要全部引入。 - 使用事件渲染函数:对于有大量事件且需要自定义样式的场景,使用
eventContent或eventDidMount/eventWillUnmount钩子比操作 DOM 更高效。 - 优化数据请求:确保从服务器获取的事件数据是精简的,只包含必要的字段,避免在每次渲染时都重新请求数据。
- 避免频繁的状态更新:在使用 React/Vue 等框架时,确保日历组件的
key属性稳定,避免不必要的重新渲染。
