引言
本文缘起自笔者开发一个基于 PIXI.js 的在线动画编辑器时,想系统学习 Canvas 相关知识,却发现缺少合适的中文入门资料,于是萌生了撰写这份"速通指北"的想法,欢迎感兴趣的朋友订阅我的 《Canvas 指北》专栏。
参考内容:
- HTML Canvas Tutorial
- Canvas API - Web API | MDN
- Canvas API - 《阮一峰 Web API 教程》
- 《从 0 到 1:HTML5 Canvas动画开发》
第1章:Canvas 简介
在这一章中,我们将一起探索 Canvas 是什么,为什么我们需要它,以及如何在 HTML 中使用 <canvas> 元素。如果你是 Web 图形开发的新手,不用担心------我们会从最基础的概念开始,逐步带你进入 Canvas 的精彩世界。
1.1 为什么需要 canvas
在 HTML5 出现之前,网页上的图形大多是静态的------图片、CSS 绘制的简单形状,或者是通过 Flash、SVG 等技术实现的动态效果。这些方法要么功能有限,要么需要额外的依赖,难以与现代 Web 应用的动态需求匹配。
Canvas(画布)是 HTML5 引入的一个革命性特性。它是一个可以通过 JavaScript 脚本在网页上绘制图形的"画板"。与传统的 DOM 元素不同,Canvas 不保存绘制的图形对象,主要通过绘图指令即时渲染,必要时也可进行像素级处理,这使得它非常适合以下场景:
- 动态数据可视化:比如绘制实时更新的图表、股票走势图、数据仪表盘。
- 游戏开发:Canvas 的高性能使其成为 2D 小游戏(如贪吃蛇、飞机大战)的常用渲染方案。
- 图像处理:可以直接在浏览器中实现滤镜、颜色调整、图像合成等功能。
- 动画与特效:可以创建粒子系统、炫酷的背景动画、交互式艺术创作。
- 绘图应用:比如在线白板、签名板、简单的画图工具。
简单来说,Canvas 让你能用 JavaScript 在网页上"画画",而且画出来的内容是动态的、可交互的,并且性能非常出色。它让网页从"展示信息"进化到了"创造视觉体验"的新阶段。
1.2 <canvas> 元素
Canvas 的使用分为两步:首先在 HTML 中放置一个 <canvas> 元素,然后通过 JavaScript 获取它的绘图上下文(context)并进行绘制。
1.2.1 HTML 中的 <canvas>标签
<canvas> 是一个行内元素(inline),但通常我们把它当作块级元素来设置宽高,实践中常设置 display: block;,可避免与文本基线对齐导致的底部空隙问题。它的基本语法如下:
html
<canvas id="myCanvas" width="400" height="300">
您的浏览器不支持 Canvas,请升级或更换浏览器。
</canvas>
- id:用于在 JavaScript 中定位该元素。
- width 和 height:指定画布的实际像素尺寸(不是 CSS 宽高)。默认值为 300×150 像素。
- 标签内的文本:当浏览器不支持 Canvas 时,会显示这段后备内容(fallback content)。
注意:尽量不要用 CSS 的 width 和 height 来修改画布的像素尺寸。如果通过 CSS 设置宽高,画布会按原始像素被拉伸,导致图形模糊。正确的做法是在
<canvas>标签中直接指定 width 和 height 属性,或者通过 JavaScript 动态设置。如果为了适配高 DPI 屏幕,通常会用 JavaScript 将 canvas 的像素尺寸设为 CSS 尺寸乘以
devicePixelRatio。
1.2.2 获取绘图上下文
在 JavaScript 中,我们通过 <canvas> 元素的 getContext() 方法获取绘图上下文。上下文定义了所有绘图方法和属性。目前最常用的是 2D 上下文:
javascript
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
除了 '2d',还有 'webgl'(用于 3D 绘图)和 'webgl2' 等上下文类型。在本教程中,我们将专注于 2D 上下文。
1.2.3 第一个绘制示例
获得了 ctx 对象后,我们就可以调用各种绘图方法了。下面是一个简单的例子:在画布上绘制一个红色的矩形。
html
<!doctype html>
<html>
<canvas
id="myCanvas"
width="200"
height="100"
style="border: 1px solid black"
>
</canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 150, 75);
</script>
</html>
这段代码会在画布的 (0,0) 位置开始,绘制一个宽 150 像素、高 75 像素的红色矩形: 
1.3 Canvas 坐标系
Canvas 的坐标系原点 (0,0) 位于画布左上角,Y 轴正方向是向下的(与常见的数学坐标系 Y 轴向上相反)。也就是说,y 值越大,图形的位置越靠下。
第2章:线条
本章带你快速掌握 Canvas 中最基础的图形------线条。我们将学习如何绘制直线,以及如何控制线条的粗细、端点样式、连接样式和虚线效果。
2.1 绘制线条
绘制线条需要掌握三个核心方法:
moveTo(x, y):将画笔移动到指定坐标(不绘制任何东西)lineTo(x, y):从当前位置绘制一条直线到指定坐标(仅定义路径,不实际描边)stroke():对当前定义的路径进行描边
此外,还需要通过 strokeStyle 设置线条颜色(默认黑色)。
画一条从 (50,30) 到 (250,120) 的红色直线:
html
<canvas id="lineCanvas" width="300" height="150"></canvas>
<script>
const canvas = document.getElementById('lineCanvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = 'red'; // 设置线条颜色
ctx.moveTo(50, 30); // 起点 (50,30)
ctx.lineTo(250, 120); // 终点 (250,120)
ctx.stroke(); // 描边绘制
</script>

注意: 以上代码未使用 beginPath(),仅适用于简单场景。如果后续需要绘制多条独立的线,必须调用 ctx.beginPath() 重置路径,否则之前绘制的线会被重复描边。我们将在第4章"路径"中详细介绍。
2.2 线条样式
2.2.1 线宽:lineWidth
设置线条的粗细(像素)。默认值为 1.0。
线宽越大,线条越粗。效果对比:
html
<canvas id="widthCanvas" width="300" height="120"></canvas>
<script>
const canvas = document.getElementById('widthCanvas');
const ctx = canvas.getContext('2d');
// 线宽 1
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(20, 20);
ctx.lineTo(280, 20);
ctx.stroke();
// 线宽 5
ctx.beginPath();
ctx.lineWidth = 5;
ctx.moveTo(20, 50);
ctx.lineTo(280, 50);
ctx.stroke();
// 线宽 10
ctx.beginPath();
ctx.lineWidth = 10;
ctx.moveTo(20, 80);
ctx.lineTo(280, 80);
ctx.stroke();
</script>

2.2.2 端点样式:lineCap
"butt"(默认):平直端点,不超出线段端点"round":圆形端点,半圆直径等于线宽"square":方形端点,超出线段端点一半线宽
画三条相同起止点的线段,分别设置不同 lineCap。效果对比:
html
<canvas id="capCanvas" width="320" height="120"></canvas>
<script>
const canvas = document.getElementById('capCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 10;
ctx.strokeStyle = 'blue';
// butt (默认)
ctx.beginPath();
ctx.lineCap = 'butt';
ctx.moveTo(40, 30);
ctx.lineTo(140, 30);
ctx.stroke();
// round
ctx.beginPath();
ctx.lineCap = 'round';
ctx.moveTo(40, 60);
ctx.lineTo(140, 60);
ctx.stroke();
// square
ctx.beginPath();
ctx.lineCap = 'square';
ctx.moveTo(40, 90);
ctx.lineTo(140, 90);
ctx.stroke();
</script>

2.2.3 连接样式:lineJoin
定义两条线相交处的拐角形状。可选值:
"miter"(默认):尖角(通过 miterLimit 限制尖锐程度)"round":圆角"bevel":平角(切去尖角)
绘制两条折线,分别应用不同连接样式。效果对比:
html
<canvas id="joinCanvas" width="320" height="200"></canvas>
<script>
const canvas = document.getElementById('joinCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 15;
// miter (默认)
ctx.beginPath();
ctx.lineJoin = 'miter';
ctx.moveTo(30, 30);
ctx.lineTo(80, 80);
ctx.lineTo(130, 30);
ctx.stroke();
// round
ctx.beginPath();
ctx.lineJoin = 'round';
ctx.moveTo(160, 30);
ctx.lineTo(210, 80);
ctx.lineTo(260, 30);
ctx.stroke();
// bevel
ctx.beginPath();
ctx.lineJoin = 'bevel';
ctx.moveTo(30, 100);
ctx.lineTo(80, 150);
ctx.lineTo(130, 100);
ctx.stroke();
</script>

2.2.4 尖角限制:miterLimit
当 lineJoin = "miter" 时,该属性限制尖角的长度。尖角长度是指内角顶点到外角顶点的距离。如果尖角长度超过 miterLimit 与线宽的乘积,则连接样式会回退为 bevel 。默认值为 10.0。
保持同一折线,用不同 miterLimit 值观察尖角变化。效果对比:
html
<canvas id="miterCanvas" width="320" height="200"></canvas>
<script>
const canvas = document.getElementById('miterCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 10;
ctx.strokeStyle = 'green';
ctx.lineJoin = 'miter'; // 必须为 miter 才生效
// miterLimit = 1(较小,尖角被截断为平角)
ctx.beginPath();
ctx.miterLimit = 1;
ctx.moveTo(30, 30);
ctx.lineTo(80, 90);
ctx.lineTo(130, 30);
ctx.stroke();
// miterLimit = 5(中等,仍为尖角)
ctx.beginPath();
ctx.miterLimit = 5;
ctx.moveTo(160, 30);
ctx.lineTo(210, 90);
ctx.lineTo(260, 30);
ctx.stroke();
// miterLimit = 10(默认,通常保持尖角)
ctx.beginPath();
ctx.miterLimit = 10;
ctx.moveTo(30, 100);
ctx.lineTo(80, 160);
ctx.lineTo(130, 100);
ctx.stroke();
</script>

2.2.5 虚线模式:setLineDash()
通过数组指定虚线的线段与间隙长度(像素)。数组元素依次为线段长、间隙长、线段长、间隙长......重复循环,若要恢复实线,可传入空数组 ctx.setLineDash([])。
绘制多条虚线,展示不同数组模式。效果对比:
html
<canvas id="dashCanvas" width="320" height="140"></canvas>
<script>
const canvas = document.getElementById('dashCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 3;
// 实线
ctx.beginPath();
ctx.setLineDash([]);
ctx.moveTo(20, 20);
ctx.lineTo(300, 20);
ctx.stroke();
// 虚线 [10, 5] (10画,5空)
ctx.beginPath();
ctx.setLineDash([10, 5]);
ctx.moveTo(20, 50);
ctx.lineTo(300, 50);
ctx.stroke();
// 点线 [2, 4] (2画,4空)
ctx.beginPath();
ctx.setLineDash([2, 4]);
ctx.moveTo(20, 80);
ctx.lineTo(300, 80);
ctx.stroke();
// 点划线 [15, 5, 2, 5] (长画,短空,点,短空)
ctx.beginPath();
ctx.setLineDash([15, 5, 2, 5]);
ctx.moveTo(20, 110);
ctx.lineTo(300, 110);
ctx.stroke();
</script>

2.2.6 虚线偏移:lineDashOffset
lineDashOffset 属性用于设置虚线模式的起始偏移量(像素),会改变虚线图案在路径上的起点位置。连续更新该值(递增或递减)可以实现"流动虚线"动画效果。
效果对比:绘制三条相同的虚线,分别设置不同偏移量。
html
<canvas id="dashOffsetCanvas" width="320" height="120"></canvas>
<script>
const canvas = document.getElementById('dashOffsetCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeStyle = 'purple';
ctx.setLineDash([10, 5]); // 统一虚线模式
// 偏移 0 (默认)
ctx.beginPath();
ctx.lineDashOffset = 0;
ctx.moveTo(20, 20);
ctx.lineTo(300, 20);
ctx.stroke();
// 偏移 5
ctx.beginPath();
ctx.lineDashOffset = 5;
ctx.moveTo(20, 50);
ctx.lineTo(300, 50);
ctx.stroke();
// 偏移 -8
ctx.beginPath();
ctx.lineDashOffset = -8;
ctx.moveTo(20, 80);
ctx.lineTo(300, 80);
ctx.stroke();
</script>

🚀 下篇预告:形状与路径篇
在下一篇中,你将学到:
- 如何绘制矩形、多边形、圆形、弧线等基本形状;
- 路径的深入用法,包括
beginPath、closePath的正确姿势; - 填充与描边的区别,以及
nonzero和evenodd两种填充规则的原理与效果; arc和arcTo的详细对比与适用场景。
掌握了这些,你就能组合路径创造出复杂图形,为后续的样式与动画打下坚实基础。