热力图本质是svg+数据映射,核心是将日期精准转为格子坐标:先归零至最近周日,再倒推51周得起始日,按“起始日+行×7+列”计算每格日期;颜色须硬编码github五级色阶;响应式用viewbox而非宽高缩放。

热力图本质是 SVG + 数据映射,不是 CSS 动画
很多人一上来就查 css heat map 或尝试用 grid + background-color 逐格设色,结果发现响应慢、缩放糊、没法按周对齐。GitHub 热力图底层是 SVG <rect></rect> 元素组成的网格,每格坐标和颜色由真实日期计算得出——关键不在“怎么画”,而在“怎么把日期转成格子位置”。
核心逻辑只有三步:生成近一年所有日期 → 按周/日定位行列 → 根据贡献数映射 fill 颜色。别碰 canvas,也别用第三方图表库,原生 SVG 足够轻、够准、够可控。
日期到格子坐标的转换必须手动算,不能依赖 getDay() 直接填
GitHub 热力图固定以周日为每周起点,且第一行永远是「最近 52 周的周日」,不是系统当前周。直接用 new Date().getDay() 会错位,尤其跨年或时区不同时。
- 先用
new Date()得到今天,再倒推到最近一个周日:date.setDate(date.getDate() - date.getDay()) - 再往前推 51 周(不是 364 天),得到第一行第一个格子的日期:
date.setDate(date.getDate() - 51 * 7) - 每个格子对应日期 = 起始周日 + 行号 × 7 + 列号(列号 0–6 对应周日到周六)
漏掉「先归零到周日再推周数」这步,整个图就会向右偏移 1–6 天,贡献全错行。
fill 颜色要按 GitHub 官方色阶硬编码,别用线性插值
GitHub 用的是离散 5 级色阶,不是连续渐变。用 hsl() 或 lch() 插值得到的颜色在低频贡献下视觉差异极小,用户根本看不出「0 次」和「1 次」的区别。
必须严格对应官方定义:
0 次 → #ebedf0<br>1–9 次 → #9be9a8<br>10–19 次 → #40c463<br>20–29 次 → #30a14e<br>30+ 次 → #216e39
注意:1–9 是闭区间,不是 ;<code>30+ 包含所有 ≥30 的值。如果后端返回的是字符串数字或 null,得先 parseInt(count) || 0 再比对。
响应式缩放要用 viewBox,别改 width/height
直接写 width="100%" height="auto" 会导致 SVG 内部比例崩坏,格子被拉扁。正确做法是固定 viewBox="0 0 750 120"(宽=7 天×100px,高=53 周×12px + 边距),然后靠容器 CSS 控制显示尺寸:
<div style="width: 100%; max-width: 750px"> <br><svg viewbox="0 0 750 120" xmlns="http://www.w3.org/2000/svg">...</svg><br> </div>
这样缩放时 SVG 自动等比适配,格子永远正方形,文字标签(如月份)也能用 font-size 配合 transform 精确对齐。移动端横屏看不清?加个 overflow-x: auto 就行,别删格子。
真正的难点从来不是画出格子,而是让每一格的日期、位置、颜色都和 GitHub API 返回的 contributionsCollection 数据严格对齐——差一天,整行就废。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











