用原生HTML+JavaScript实现可点击、带当前月高亮的日历,需三步:生成指定年月日期数组→渲染为table→绑定点击与翻页事件;关键用new Date(year, month, 1).getDay()算1号星期几,new Date(year, month + 1, 0).getDate()得当月天数,用toDateString()比对“今天”;注意JS月份0–11索引,UI显示需+1,传参需-1。
纯 HTML 没有内置日历组件,<input type="date"> 是浏览器原生控件,但无法自定义样式、不能点选多日、也不支持月份切换。真要可控,必须手写渲染表格 + JS 控制逻辑。
核心就三步:生成指定年月的日期数组 → 渲染成 <table> → 绑定点击和翻页事件。别从零写 DOM 操作,用 document.createElement 或模板字符串拼接都行,关键是把 1 号星期几、当月天数、是否为今天这些状态算准。
new Date(year, month, 1).getDay() 得到该月 1 日是周几(0=周日),用来补空格单元格new Date(year, month + 1, 0).getDate() 得到当月总天数(比 getDaysInMonth 兼容性好)date.toDateString() === new Date().toDateString(),避免时区或时间部分干扰getMonth() 返回 0–11,但 new Date(2024, 1, 1) 是 2 月而不是 1 月这是 JavaScript Date API 的经典陷阱:月份索引从 0 开始,但人类读日历时默认 1 月是第 1 个月。结果就是传参和取值不一致,一不留神就错一个月。
实际写日历时,建议统一用“显示月份 = JS 月份 + 1”,所有 UI 上展示的月份数字都要加 1;而传给 Date 构造函数或 setMonth() 时,必须减 1。比如用户点“3 月”,JS 内部存的是 month = 2。
立即学习“前端免费学习笔记(深入)”;
${year} 年 ${month + 1} 月
new Date(year, month, day)(month 已是 0–11)month--,但要检查 if (month
"2024-03-15")不要拼接字符串或用 toLocaleDateString(),它受系统 locale 影响,中文环境可能输出 "2024/3/15" 或带星期。要稳定输出 ISO 格式,直接用 date.toISOString().slice(0, 10) 最省事。
前提是你的点击事件里能拿到那个日期对应的 Date 对象。常见错误是只存了“日”(如 15),没存年月,导致构造新 Date 时默认用当前年月,点去年 12 月的 25 日却返回今年的日期。
<td> 时,用 data-date 属性存完整 ISO 字符串:<td data-date="2024-02-29">29</td>
const iso = e.target.dataset.date,直接可用,不用再解析new Date(year, month, day)
日历单元格太小(尤其手机),touchstart 和 click 事件容易误触发或延迟。hover 在触摸设备上基本无效,不能靠它做交互反馈。
解决方案很实在:加大点击热区、用 class 切换代替 hover、加防抖。别指望 CSS 的 :hover 在 iPad 或安卓 Chrome 上可靠工作。
<td> 加 min-width: 40px; min-height: 40px;,配合 padding 扩大可点区域td.selected class 控制背景色,JS 中 el.classList.toggle('selected')
prevMonth/nextMonth 加 setTimeout 防抖(300ms 内只响应一次)最麻烦的其实是跨时区场景——用户在北京点 3 月 1 日,服务端收到的可能是 UTC 时间 2 月 29 日。如果业务强依赖本地日期(比如请假单),所有日期处理必须基于 date.getFullYear()、getMonth()、getDate(),避开 toISOString() 或 getTime() 这类转 UTC 的方法。