非线性缩放需手动设计坐标映射逻辑,通过分段绘制或连续函数实现不同位置差异化缩放,解决长图在小画布中兼顾全局与局部清晰度的难题。

非线性缩放不是 Canvas 原生支持的功能,它需要你主动设计缩放逻辑——比如让画布中心区域放大、边缘区域压缩,或按自定义曲线(如对数、平方根)映射坐标。这种技术特别适合长图在小尺寸画布中“既看清全局、又聚焦局部”,比如地图概览+重点区域高精度展示、时间轴图表、超长流程图等场景。
明确目标:长图 ≠ 等比压缩
直接用 ctx.scale() 是线性缩放,整张图统一拉伸或缩小,无法解决“小画布里既要看到全部内容、又要让中间一段清晰可读”的矛盾。非线性缩放的核心是:**同一张长图,不同纵向(或横向)位置应用不同的缩放系数**。
- 例如:一张 2000px 高的长图,要放进 400px 高的 canvas;不希望顶部和底部被严重压缩看不清文字,也不希望中间被拉得模糊——那就让 y=200px 附近缩放系数为 1.5,y=0 和 y=400 处缩放系数降到 0.6,中间平滑过渡
- 关键不是“画布变大”,而是“坐标映射规则变了”:你给一个逻辑 y 坐标,Canvas 不再直接对应像素 y,而是查表或计算后得到真实绘制位置
实现方式:分段映射 + 逐段重绘
Canvas 本身不提供非线性变换矩阵,所以必须手动拆解长图,按不同缩放比例分段绘制:
- 将长图按逻辑高度切分成若干段(如每段 200px 逻辑高度),每段指定独立缩放因子 s₁, s₂, s₃…
- 计算每段在 canvas 中的绘制高度:
renderHeight = segmentLogicalHeight × sᵢ,并累加确定起始 y 像素位置 - 用
ctx.drawImage(img, sy, sh, 0, renderY, img.width, renderHeight)分别绘制每一段,其中sy是该段在原图中的起始行,sh是该段逻辑高度 - 避免留白或重叠:确保所有
renderHeight总和 ≈ canvas.height,且renderY累加准确
坐标反向映射:点击/拖拽必须能定位原图
用户在 canvas 上点击某点 (clientX, clientY),你要算出它对应原图的哪个像素位置——这不能靠 scale() 自动处理,必须写反函数:
立即学习“前端免费学习笔记(深入)”;
- 先通过
canvas.getBoundingClientRect()得到点击点相对于 canvas 左上角的yCanvas - 遍历每一段渲染区域,找到
yCanvas落在哪一段(比如第 2 段,起始 renderY=85,高度 120) - 算出在该段内的局部偏移:
localY = yCanvas - 85,再除以该段缩放系数:logicalY = localY / s₂ - 最后加上该段在原图中的起始逻辑行:
imgY = segmentStartY + logicalY
优化体验:加过渡与缓动
纯分段会带来视觉断层。可在段与段之间加入 20–40px 重叠区,用渐变缩放系数(如线性插值)过渡:
- 定义缩放函数
f(y) = a + b × Math.sqrt(y)或f(y) = 1 + 0.5 × Math.sin((y / height) × π),让缩放随位置平滑变化 - 绘制时不再分段,而是按小步长(如每 2px)计算当前 y 对应的缩放值,用
drawImage绘制高度为 2px 的细条,动态调整sh和height - 性能敏感时,可预生成缩放映射表(Array),用查表替代实时计算



















