用原生JavaScript实现日历组件,核心是通过Date对象处理日期相关计算,再结合DOM操作渲染视图,支持基础的上月、下月切换和日期选中功能。

一、实现思路梳理
日历组件的核心逻辑可以分为以下几个部分:
- 维护当前显示的年份和月份状态,默认初始化为当前系统日期
- 计算当前月份的总天数,以及当月第一天是星期几,用于确定日历表格的起始位置
- 生成6行7列的日历表格结构,填充上月的尾部日期、当月所有日期、下月的头部日期
- 绑定上月、下月按钮的点击事件,更新状态并重新渲染日历
- 绑定日期单元格的点击事件,实现选中状态切换
二、基础样式准备
先写基础的日历样式,保证布局清晰:
/* 日历容器 */
.calendar {
width: 320px;
border: 1px solid #e5e5e5;
border-radius: 4px;
padding: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* 头部 */
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.calendar-header button {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
}
.calendar-header button:hover {
background: #f5f5f5;
}
/* 星期行 */
.week-row {
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
margin-bottom: 8px;
color: #666;
font-size: 14px;
}
/* 日期表格 */
.date-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-template-rows: repeat(6, 1fr);
gap: 4px;
}
.date-cell {
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.date-cell.current-month {
color: #333;
}
.date-cell.other-month {
color: #ccc;
}
.date-cell.selected {
background: #1890ff;
color: #fff;
}
.date-cell:hover:not(.selected) {
background: #f0f0f0;
}三、JavaScript核心逻辑实现
接下来实现日历的核心功能代码:
class Calendar {
constructor(containerId) {
// 获取容器元素
this.container = document.getElementById(containerId);
// 初始化当前日期为系统当前日期
const now = new Date();
this.currentYear = now.getFullYear();
this.currentMonth = now.getMonth(); // 0-11 表示1-12月
this.selectedDate = null; // 选中的日期
this.init();
}
// 初始化组件
init() {
this.render();
}
// 渲染整个日历
render() {
this.container.innerHTML = '';
// 渲染头部
const header = this.renderHeader();
// 渲染星期行
const weekRow = this.renderWeekRow();
// 渲染日期网格
const dateGrid = this.renderDateGrid();
this.container.appendChild(header);
this.container.appendChild(weekRow);
this.container.appendChild(dateGrid);
}
// 渲染头部:显示年月+切换按钮
renderHeader() {
const header = document.createElement('div');
header.className = 'calendar-header';
const prevBtn = document.createElement('button');
prevBtn.textContent = '上月';
prevBtn.addEventListener('click', () => this.switchMonth(-1));
const nextBtn = document.createElement('button');
nextBtn.textContent = '下月';
nextBtn.addEventListener('click', () => this.switchMonth(1));
const title = document.createElement('div');
title.textContent = `${this.currentYear}年${this.currentMonth + 1}月`;
header.appendChild(prevBtn);
header.appendChild(title);
header.appendChild(nextBtn);
return header;
}
// 渲染星期行
renderWeekRow() {
const weekRow = document.createElement('div');
weekRow.className = 'week-row';
const weeks = ['日', '一', '二', '三', '四', '五', '六'];
weeks.forEach(week => {
const cell = document.createElement('div');
cell.textContent = week;
weekRow.appendChild(cell);
});
return weekRow;
}
// 渲染日期网格
renderDateGrid() {
const dateGrid = document.createElement('div');
dateGrid.className = 'date-grid';
// 获取当月第一天是星期几
const firstDay = new Date(this.currentYear, this.currentMonth, 1).getDay();
// 获取当月总天数
const currentMonthDays = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
// 获取上月总天数
const prevMonthDays = new Date(this.currentYear, this.currentMonth, 0).getDate();
// 生成所有日期
const dates = [];
// 上月尾部日期
for (let i = firstDay - 1; i >= 0; i--) {
dates.push({
day: prevMonthDays - i,
month: this.currentMonth - 1,
year: this.currentMonth === 0 ? this.currentYear - 1 : this.currentYear,
isCurrentMonth: false
});
}
// 当月所有日期
for (let i = 1; i <= currentMonthDays; i++) {
dates.push({
day: i,
month: this.currentMonth,
year: this.currentYear,
isCurrentMonth: true
});
}
// 下月头部日期,凑满42个单元格(6行7列)
const remainCount = 42 - dates.length;
for (let i = 1; i <= remainCount; i++) {
dates.push({
day: i,
month: this.currentMonth + 1,
year: this.currentMonth === 11 ? this.currentYear + 1 : this.currentYear,
isCurrentMonth: false
});
}
// 生成日期单元格
dates.forEach(dateItem => {
const cell = document.createElement('div');
cell.className = 'date-cell';
if (dateItem.isCurrentMonth) {
cell.classList.add('current-month');
} else {
cell.classList.add('other-month');
}
// 判断是否为选中日期
if (this.selectedDate &&
this.selectedDate.year === dateItem.year &&
this.selectedDate.month === dateItem.month &&
this.selectedDate.day === dateItem.day) {
cell.classList.add('selected');
}
cell.textContent = dateItem.day;
// 绑定点击选中事件
cell.addEventListener('click', () => {
this.selectedDate = dateItem;
this.render();
});
dateGrid.appendChild(cell);
});
return dateGrid;
}
// 切换月份
switchMonth(step) {
this.currentMonth += step;
// 处理月份越界
if (this.currentMonth > 11) {
this.currentMonth = 0;
this.currentYear += 1;
} else if (this.currentMonth < 0) {
this.currentMonth = 11;
this.currentYear -= 1;
}
this.render();
}
}
// 初始化日历,容器id为calendar-container
new Calendar('calendar-container');四、使用示例
在HTML中只需要准备一个容器元素,引入样式和JS代码即可运行:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生JS日历组件</title>
<style>
/* 这里放入上面的CSS样式代码 */
</style>
</head>
<body>
<div id="calendar-container" class="calendar"></div>
<script>
/* 这里放入上面的JavaScript代码 */
</script>
</body>
</html>五、功能扩展建议
上述代码实现了日历的基础功能,还可以根据需求扩展更多能力:
- 添加今天按钮,快速跳转到当前日期
- 支持标记特殊日期,比如 holidays 数组传入需要标记的日期,渲染时添加特殊样式
- 添加日期范围选择功能,支持选中开始和结束两个日期
- 增加月份快速选择器,支持直接选择年份和月份
整个实现过程没有依赖任何第三方库,核心逻辑围绕Date对象的日期计算和DOM操作展开,理解后可以灵活调整适配不同的业务需求。
JavaScript日历组件Date对象前端开发DOM操作修改时间:2026-06-04 03:33:01