Canvas 基础:坐标系、像素与 2D 绘制上下文
<canvas>是前端里最基础也最容易被忽略的一块:看起来只是一个能画图的标签,但背后有一整套关于坐标系、像素密度和状态管理的规则。
这一篇从三个问题入手:Canvas 在页面中的角色是什么、默认坐标系和像素是怎么定义的,以及如何用 2D 上下文的基础 API 画出第一批图形。
1. <canvas> 元素和 2D 上下文是什么关系?
在 DOM 里,Canvas 只是一个普通元素:
- 通过
width和height属性决定画布的逻辑尺寸(以像素为单位); - 通过 CSS 再决定它在页面上的展示尺寸(可能被缩放)。
想在上面画东西,需要获取「绘制上下文」:
1const canvas = document.getElementById("canvas");
2const ctx = canvas.getContext("2d");
这里的 ctx 就是 Canvas 2D API 的入口,后续所有画线、填充、写字、绘图等操作都通过它来完成。
可以把结构简单理解为:
<canvas>:一块「像素缓冲区」;ctx(2D 上下文):一支「画笔」,提供一整套绘图命令。
2. 坐标系与像素:左上角是原点,一切都是浮点数
2.1 默认坐标系
在 2D 上下文中,默认坐标系是:
- 原点
(0, 0)在画布左上角; - x 轴向右增大;
- y 轴向下增大。
所有绘图 API(例如 fillRect(x, y, width, height))的参数都基于这个默认坐标系:
x、y是矩形左上角的位置;width、height是宽高。
2.2 坐标是浮点数,像素是离散的
Canvas API 的坐标和尺寸参数都是浮点数,但最终渲染时需要映射到离散像素上:
- 在非整数坐标绘制 1 像素线条时,可能会出现「模糊」或「半像素」的效果;
- 通常建议在需要精确 1 像素线条时,注意坐标的对齐(例如
0.5偏移的用法)。
理解这一点,在后续做高清适配或细节绘制时会更顺手。
3. 逻辑尺寸与 CSS 尺寸:devicePixelRatio 的影响
3.1 为什么同样大小在高清屏上会模糊?
Canvas 的逻辑尺寸(canvas.width / canvas.height)和 CSS 尺寸(style.width / style.height)在高 DPI 设备上经常不一致:
- CSS 上看是 300×150;
- 实际像素可能是两倍(例如 Retina 屏上常见的 600×300)。
如果不做适配,1 逻辑像素对应多物理像素,会导致:
- 所有内容被浏览器拉伸,看起来发糊。
3.2 基于 devicePixelRatio 的简单适配思路
典型做法是:
- 将 Canvas 的实际宽高设为 CSS 宽高乘以
window.devicePixelRatio; - 再通过
scale把 2D 上下文坐标系调回到逻辑尺寸。
示例思路如下:
1const canvas = document.getElementById("canvas");
2const ctx = canvas.getContext("2d");
3
4const dpr = window.devicePixelRatio || 1;
5const rect = canvas.getBoundingClientRect();
6
7canvas.width = rect.width * dpr;
8canvas.height = rect.height * dpr;
9
10ctx.scale(dpr, dpr);
这样即便在高 DPI 设备上,1 个逻辑单位仍对应视觉上的 1 像素,但背后有更多物理像素用于绘制,从而保持清晰。
4. 基础绘制 API:矩形、路径与颜色
Canvas 2D API 提供了几组基础绘图命令,可以先掌握三类:
- 直接绘制矩形;
- 使用路径绘制任意形状;
- 设置填充与描边样式。
4.1 矩形家族
矩形是最简单的图形,常用三个方法:
fillRect(x, y, width, height):绘制填充矩形;strokeRect(x, y, width, height):绘制描边矩形;clearRect(x, y, width, height):清除一个区域(使其变透明)。
之前设置的 fillStyle 和 strokeStyle 会影响填充和描边颜色。
4.2 路径:从点线组合成任意形状
更复杂的图形需要用路径来构建:
beginPath():开始一条新路径;moveTo(x, y):把画笔移动到某个点(不画线);lineTo(x, y):从当前点画一条线到指定位置;arc(...)/rect(...)/quadraticCurveTo(...)/bezierCurveTo(...):画圆弧、矩形、曲线等;closePath():闭合路径;fill():填充路径内部;stroke():沿路径描边。
路径的核心是:
- 先用一组命令构造出「轮廓」;
- 然后选择填充或描边方式进行输出。
4.3 颜色与线条样式
常用的样式属性包括:
fillStyle:填充颜色,可以是字符串(如'#ff0000'、'rgba(...)')、渐变或图案;strokeStyle:描边颜色;lineWidth:线宽;lineJoin、lineCap:线连接方式与端点样式。
这些属性会作用于后续的 fill() / stroke() / fillRect() / strokeRect() 调用,直到被修改或状态被恢复。
5. 状态的概念:Canvas 是有「记忆」的
Canvas 上下文内部有一整套状态:
- 当前变换矩阵(平移、旋转、缩放);
- 填充/描边样式;
- 线条参数、阴影、全局透明度等。
当调用绘图 API 时,都会使用当前状态:
- 先设置状态,再调用绘图命令;
- 状态会一直保留,除非显式修改或通过
save()/restore()管理。
在简单场景里可以不太在意这点,但在稍复杂的绘制中,良好的状态管理能避免很多「为什么后面的线条颜色也变了」之类的问题。
6. 小结:第一步先把「画布」这件事弄清楚
这一篇可以压缩成几条关键记忆点:
<canvas>+2d上下文的关系:一个是像素缓冲区,一个是绘图 API;- 默认坐标系原点在左上角,x 向右、y 向下,坐标与尺寸都是浮点数;
- 逻辑尺寸与 CSS 尺寸配合
devicePixelRatio决定了绘制清晰度; - 最基础的绘图能力来自矩形和路径 API,配合样式属性与状态概念使用。
在这些基础之上,后续再看变换矩阵、动画、交互和性能优化时,就不会在「坐标和像素到底怎么回事」上纠缠。