本文详解如何在 fullcalendar 中自定义月视图(daygridmonth)页眉,实现在标题区域动态显示「上上月、上月、本月、下月、下下月」共五个连续月份名称,并确保翻页时实时更新——无需依赖插件,纯原生 js + moment.js 即可实现。
本文详解如何在 fullcalendar 中自定义月视图(daygridmonth)页眉,实现在标题区域动态显示「上上月、上月、本月、下月、下下月」共五个连续月份名称,并确保翻页时实时更新——无需依赖插件,纯原生 js + moment.js 即可实现。
FullCalendar 默认的 headerToolbar 仅支持 prev, next, title 等基础按钮与占位符,无法直接渲染相邻月份名称(如“June July August September October”)。要实现题中需求——页眉显示类似 prevbut July August September nextbut 的结构,需绕过内置 title 渲染机制,采用「手动 DOM 绑定 + 日历实例状态监听」的方式动态更新文本。
✅ 实现原理简述
- 禁用默认标题栏:将 headerToolbar 的 left/center/right 全部设为空对象 {},避免干扰;
-
自定义 HTML 结构:在日历容器上方(或内部)预置带 ID 的 元素,例如:
<div class="custom-header"> <button id="prevButton">◀</button> <span id="previous-month-2"></span> <span id="previous-month-1"></span> <span id="current-month"></span> <span id="next-month-1"></span> <span id="next-month-2"></span> <button id="nextButton">▶</button> </div> <div id="calendar"></div>
- 初始化时计算并填充月份:调用 calendar.getDate() 获取当前日期,借助 moment()(或现代替代方案 dayjs/date-fns)生成偏移后的月份名;
- 绑定翻页事件监听器:为自定义 prevButton 和 nextButton 添加点击事件,在调用 calendar.prev() / calendar.next() 后,重新获取当前日期 → 计算所有五个月份 → 更新对应 DOM 文本。
? 关键代码实现(适配 FullCalendar v5+)
⚠️ 注意:以下代码假设已引入 Moment.js(v2.x)或使用 date-fns 替代(文末提供迁移建议)
function drawCalendar() {
const calendarEl = document.getElementById('calendar');
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
fixedWeekCount: false,
firstDay: 1,
displayEventTime: false,
height: 700,
dayMaxEventRows: true,
views: {
timeGrid: { dayMaxEventRows: 6 }
},
// ? 完全禁用默认 headerToolbar
headerToolbar: { left: '', center: '', right: '' },
events: eventsArray,
eventClick: showEvent,
eventDidMount: ({ event, el }) => {
if (event.backgroundColor) el.style.background = event.backgroundColor;
}
});
// ✅ 预先获取 DOM 元素引用(提升性能)
const prevMonthEl2 = document.getElementById('previous-month-2');
const prevMonthEl1 = document.getElementById('previous-month-1');
const currentMonthEl = document.getElementById('current-month');
const nextMonthEl1 = document.getElementById('next-month-1');
const nextMonthEl2 = document.getElementById('next-month-2');
// ✅ 初始化月份文本(首次加载)
function updateMonthLabels() {
const currentDate = calendar.getDate();
const m = moment(currentDate);
prevMonthEl2.textContent = m.clone().subtract(2, 'months').format('MMMM');
prevMonthEl1.textContent = m.clone().subtract(1, 'months').format('MMMM');
currentMonthEl.textContent = calendar.formatDate(currentDate, { month: 'long' });
nextMonthEl1.textContent = m.clone().add(1, 'months').format('MMMM');
nextMonthEl2.textContent = m.clone().add(2, 'months').format('MMMM');
}
// ✅ 绑定翻页逻辑
document.getElementById('prevButton').addEventListener('click', () => {
calendar.prev();
updateMonthLabels(); // 翻页后立即刷新
});
document.getElementById('nextButton').addEventListener('click', () => {
calendar.next();
updateMonthLabels();
});
// ? 首次渲染前更新(确保初始状态正确)
updateMonthLabels();
calendar.render();
}
drawCalendar();
⚠️ 注意事项与最佳实践
时区安全:moment(date).format('MMMM') 默认使用浏览器本地时区;若需服务端统一时区,请显式调用 .utc() 或配置 moment.tz()。
国际化(i18n)支持:calendar.formatDate() 自动适配 locale 配置(如 'ru'),但 moment().format() 需额外加载对应 locale 文件(如 moment/locale/ru)。
性能优化:避免在事件回调中重复创建 moment 实例;使用 clone() 而非多次 moment(currentDate)。
-
Modern 替代方案(推荐):
Moment.js 已进入维护模式。建议迁移到轻量级库:// 使用 date-fns(ESM) import { format, addMonths, subMonths } from 'date-fns'; import { ru } from 'date-fns/locale'; const now = calendar.getDate(); prevMonthEl1.textContent = format(subMonths(now, 1), 'LLLL', { locale: ru }); currentMonthEl.textContent = format(now, 'LLLL', { locale: ru }); 无障碍(a11y)增强:为自定义按钮添加 aria-label(如 aria-label="上个月"),并确保键盘可访问(tabindex="0" + keydown 支持 Enter/Space)。
✅ 总结
FullCalendar 的灵活性在于它不强制封装所有 UI 场景——当内置配置无法满足复杂页眉需求时,主动接管 DOM 控制权反而是更清晰、更可控的方案。通过分离「日历逻辑」与「UI 渲染」,你不仅能实现多月份标题,还可轻松扩展为季度切换、年份范围选择等高级功能。记住核心原则:用 calendar.getDate() 获取权威时间源,用轻量日期库做格式化,用事件监听保证状态同步——即可稳健驾驭任意定制化场景。










