
本文详解如何在 fullcalendar 中自定义月视图(daygridmonth)页眉,实现类似 “prev ← june july august → next” 的三月联动标题栏,涵盖初始化渲染、按钮交互及动态月份更新逻辑。
本文详解如何在 fullcalendar 中自定义月视图(daygridmonth)页眉,实现类似 “prev ← june july august → next” 的三月联动标题栏,涵盖初始化渲染、按钮交互及动态月份更新逻辑。
在默认配置下,FullCalendar 的 headerToolbar 仅支持 prev, next, title 等内置按钮与占位符,无法直接通过 titleFormat 或 headerToolbar.left/center/right 渲染“前一月 + 当前月 + 后一月”的组合标题。要实现如 "prevbut July August September nextbut" 的效果,必须脱离纯配置驱动,采用 DOM 手动注入 + 日历实例事件监听 的混合方案。
✅ 核心思路
- 禁用默认标题栏:将 headerToolbar 设为全空(left: '', center: '', right: ''),避免原生 title 占位符干扰;
- 预设 HTML 容器:在页面中提前声明带 ID 的 <span> 元素,用于承载各月份文本;
- 初始化时计算并填充:调用 calendar.getDate() 获取当前日期,借助 moment.js(或原生 Intl.DateTimeFormat)格式化前后月份;
- 绑定导航事件:监听 prevButton / nextButton 点击,调用 calendar.prev() / calendar.next() 后,重新计算并更新所有月份 DOM 节点。
⚠️ 注意:calendar.next() 和 calendar.prev() 是异步导航方法,但不触发重绘回调;因此必须在调用后主动获取新日期并刷新文本,不可依赖 datesSet 钩子(因其在渲染后才触发,易导致短暂错位)。
? HTML 结构示例(需提前写入页面)
<div class="custom-header"> <button id="prevButton" class="fc-button">◀</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" class="fc-button">▶</button> </div> <div id="calendar"></div>
? JavaScript 实现(含容错与本地化支持)
以下代码基于 FullCalendar v5+(兼容 moment.js 2.x),已优化重复逻辑并增强健壮性:
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 getEl = id => document.getElementById(id);
const prevMonthEl2 = getEl('previous-month-2');
const prevMonthEl1 = getEl('previous-month-1');
const currentMonthEl = getEl('current-month');
const nextMonthEl1 = getEl('next-month-1');
const nextMonthEl2 = getEl('next-month-2');
// 月份格式化工具函数(推荐使用原生 Intl,避免 moment 依赖)
const formatMonth = (date, locale = 'ru') => {
return new Intl.DateTimeFormat(locale, { month: 'long' }).format(date);
};
// 更新所有月份文本
const updateMonthLabels = () => {
const currentDate = calendar.getDate();
const current = new Date(currentDate);
// 计算前后月份(注意:setMonth 会修改原对象,故用 clone)
const prev2 = new Date(current); prev2.setMonth(current.getMonth() - 2);
const prev1 = new Date(current); prev1.setMonth(current.getMonth() - 1);
const next1 = new Date(current); next1.setMonth(current.getMonth() + 1);
const next2 = new Date(current); next2.setMonth(current.getMonth() + 2);
prevMonthEl2.textContent = formatMonth(prev2, 'ru');
prevMonthEl1.textContent = formatMonth(prev1, 'ru');
currentMonthEl.textContent = formatMonth(current, 'ru');
nextMonthEl1.textContent = formatMonth(next1, 'ru');
nextMonthEl2.textContent = formatMonth(next2, 'ru');
};
// 初始化渲染
calendar.render();
updateMonthLabels();
// 绑定导航按钮
getEl('prevButton')?.addEventListener('click', () => {
calendar.prev();
updateMonthLabels(); // 导航后立即刷新
});
getEl('nextButton')?.addEventListener('click', () => {
calendar.next();
updateMonthLabels();
});
}
drawCalendar();? 关键注意事项
- 时区安全:calendar.getDate() 返回的是本地时区的 Date 对象,Intl.DateTimeFormat 默认按浏览器时区格式化,无需额外处理;
- 无 moment.js 依赖:上述代码使用原生 Intl.DateTimeFormat,轻量且支持多语言(传入 'en-US' 即可切换英文);
- 性能提示:若日历频繁切换(如动画过渡),可节流 updateMonthLabels 调用,但普通月粒度切换无需优化;
- 样式控制:.custom-header 可通过 CSS 灵活布局(如 display: flex; gap: 8px; align-items: center;),按钮建议复用 FullCalendar 原生样式类(如 fc-button)保持 UI 一致性;
- 响应式适配:小屏设备可隐藏 prevMonthEl2 / nextMonthEl2,仅保留 prev1 | current | next1,提升可读性。
✅ 总结
FullCalendar 的页眉定制虽不提供开箱即用的“三月标题”选项,但通过解耦渲染逻辑与 DOM 操作,配合精准的日期计算和事件监听,即可稳定实现专业级月视图导航体验。该方案完全兼容官方 API,无 hack 行为,易于维护与国际化扩展——是前端开发者掌握日历深度定制能力的典型实践路径。

















