真正卡住开发的是首日偏移、跨月样式、等高对齐和空单元格处理;需用date.getday()计算偏移并设置grid-column-start,minmax(40px, auto)控制行高,data-month属性区分月份,aria-hidden="true"处理空格。

直接用 grid-template-columns: repeat(7, 1fr) 能画出七列骨架,但真正卡住开发的从来不是列数——而是首日偏移错位、跨月语义断裂、Safari 下行高塌陷、键盘无法聚焦空格这四件事。
怎么让 1 号自动落在对应星期列(不插空 div)
Grid 不认识“星期”,只按 DOM 顺序填格子。如果某月 1 号是周三(date.getDay() === 3),它必须落在第 4 列(列号从 1 开始),否则整个月错位。
- 算偏移量:
firstDayOffset = date.getDay() === 0 ? 6 : date.getDay() - 1(按周一为周首) - 给第一个日期元素加
grid-column-start: calc(<code>firstDayOffset + 1) - 后续日期保持自然 DOM 顺序,Grid 自动按
grid-auto-flow: row填充,无需任何占位元素 - 别用
<div class="empty"></div>:增加无意义 DOM、干扰屏幕阅读器、Tab 键会停在空格上
为什么 grid-auto-rows: minmax(40px, auto) 比 1fr 更合适
1fr 在 grid-auto-rows 中会让每行均分容器高度,但日历不是全屏表格——它需要“最小可读高度 + 内容撑开”的弹性。
-
minmax(40px, auto)保证每个格子至少 40px 高,文字不挤;内容多时(如带事件标签的日期)可自然扩展 -
1fr容易导致空行被拉得过高,或内容超长时整行失衡(尤其在多周视图中) - Safari 对
minmax(min, auto)的auto支持弱,若遇塌陷,可降级为minmax(40px, max-content),但注意旧版 Chrome 兼容性差 - 绝对不要设固定
height:会截断内容、破坏可访问性缩放、移动端字体变大后溢出
如何区分上月/本月/下月而不靠 JS 加 class
用 HTML 属性驱动样式,比动态加 class 更轻量、更利于 SSR 和缓存。
- 渲染每个日期时带上
data-month="prev"/"current"/"next" - CSS 直接匹配:
[data-month="prev"] { opacity: 0.45; color: #999; },[data-month="next"] { pointer-events: none; } - 绝不用
:nth-child(8)这类伪类——它只数 DOM 顺序,无法反映真实日期逻辑,跨月就失效 - 若需深色模式支持,用
color-scheme: light dark配合@media (prefers-color-scheme: dark)微调即可
小屏下 grid-auto-flow: column 为什么还是错位
只改 grid-auto-flow: column 不够,它只是改变填充方向,不会重排列数或处理标题语义。
- 必须同步把
grid-template-columns改为1fr,否则仍保留 7 列导致横向溢出 - 星期标题需单独包裹(例如用
<div class="weekdays">),避免被 column 流打乱顺序<li>触摸热区至少 44px 高:<code>padding不够时用min-height: 44px补,别依赖transform: scale()(它不放大点击区域) - 为每个日期元素加
tabindex="0"和:focus-visible样式,否则键盘用户根本没法用方向键切日期
最常被忽略的是:Grid 本身不提供焦点流,也不理解“空单元格”语义——aria-hidden="true" 必须手动加在非当前月且无交互的格子上,否则屏幕阅读器会误读空白。











