canvas绘制动态热力图的关键在于像素权重精准计算、可插值颜色梯度映射及渐变衰减帧控:用离屏canvas或uint8clampedarray构建灰度缓冲区,以createradialgradient预绘衰减核并按value缩放globalalpha累加;色标采用hsl空间线性插值(含0°/360°短弧处理)并预生成256级lut;动态更新通过requestanimationframe驱动半透明覆盖与局部叠加实现自然淡出。

Canvas 绘制动态热力图的关键,不在画得快,而在每个像素的权重算得准、颜色映射得稳。
像素权重怎么算:不是“画点”,而是“投影响”
热力图本质是空间密度分布,单个数据点不是打一个实心圆,而是在周围区域投射一个带衰减的影响力。这个影响力决定每个像素最终叠加的强度值。
- 用离屏 Canvas 或 Uint8ClampedArray 构建灰度缓冲区,避免反复调用 getImageData 性能损耗
- 对每个数据点,用 createRadialGradient 预绘一个中心 alpha=1、边缘 alpha=0 的灰度核(半径建议 15–30px,依数据密度调整)
- 按点的 value 缩放 globalAlpha:alpha = (value − min) / (max − min),保证小值可见、大值不溢出
- 所有点逐个绘制到灰度缓冲区,像素值整数累加(如 Uint8ClampedArray[i] += Math.round(alpha * 255)),天然支持叠加
色彩映射怎么配:别硬写 rgba,要可插值、可截断
颜色不是靠几个固定色块拼接,而是把归一化后的灰度值 t ∈ [0, 1] 映射到 RGB 空间——关键在插值方式和锚点设计。
- 定义色标数组,例如 [[0, [0,0,255,128]], [0.4, [0,255,255,192]], [0.7, [255,255,0,224]], [1, [255,0,0,255]]],每个含位置与 RGBA
- t 落在哪一段,就线性插值两端 R/G/B/A,避免跳变;A 值随强度上升增强(如 0.3→1.0),防止低值区域发虚
- 优先用 HSL 插值:RGB 直接插值在黄→红段易发灰,HSL 中对色相做跨 0°/360°短弧处理(如 350°→10° 按 +20° 算),更顺滑
- 性能敏感场景预生成 256 级 LUT:for i=0..255,计算 t=i/255 对应的 [r,g,b],查表取色比实时计算快一个数量级
动态更新怎么稳:帧控不是“重画”,而是“渐变衰减”
实时热力图卡顿,往往因为每帧全量重建。真正高效的做法,是让旧数据自然淡出、新数据局部叠加。
- 用 requestAnimationFrame 驱动,禁用 setInterval;每帧先用半透明黑层覆盖:ctx.fillStyle = 'rgba(0,0,0,0.03)'; ctx.fillRect(0,0,w,h)
- 新数据只更新受影响像素块(如以点为中心 ±radius 区域),不碰其余部分
- 老数据不用删,用时间衰减因子降低权重:当前帧权重 × e⁻ᵗ,t 是距采集时刻的秒数,指数衰减比线性更符合感知
- 若数据变化慢(如每秒新增 ≤100 点),可跳过全量归一化,仅维护 maxVal 滑动窗口,复用旧 LUT
坐标怎么落到像素上:错一位,偏一格
原始数据(经纬度、clientX/Y、网格索引)必须统一转为 Canvas 内整数像素坐标,否则模糊、偏移、采样错位全来。
- 地图场景:用墨卡托投影将 {lng, lat} → {x, y},缩放级别变化时必须重算,不可缓存静态像素
- 网页行为:event.clientX/clientY 减去 canvas.getBoundingClientRect().left/top,再 × devicePixelRatio 适配高清屏
- 网格数据(如 10×12 风速阵列):x = colIndex × (canvas.width / colCount),y = rowIndex × (canvas.height / rowCount),用 Math.floor 或 Math.round 控制取整,避免空行空列











