速通Canvas指北🦮——基础入门篇

21 阅读8分钟

引言

本文缘起自笔者开发一个基于 PIXI.js 的在线动画编辑器时,想系统学习 Canvas 相关知识,却发现缺少合适的中文入门资料,于是萌生了撰写这份“速通指北”的想法,欢迎感兴趣的朋友订阅我的 《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;,可避免与文本基线对齐导致的底部空隙问题。它的基本语法如下:

<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 上下文:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

除了 '2d',还有 'webgl'(用于 3D 绘图)和 'webgl2' 等上下文类型。在本教程中,我们将专注于 2D 上下文。

1.2.3 第一个绘制示例

获得了 ctx 对象后,我们就可以调用各种绘图方法了。下面是一个简单的例子:在画布上绘制一个红色的矩形。

<!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) 的红色直线:

<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。

线宽越大,线条越粗。效果对比:

<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。效果对比:

<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>

image.png

2.2.3 连接样式:lineJoin

定义两条线相交处的拐角形状。可选值:

  • "miter"(默认):尖角(通过 miterLimit 限制尖锐程度)
  • "round":圆角
  • "bevel":平角(切去尖角)

绘制两条折线,分别应用不同连接样式。效果对比

<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 值观察尖角变化。效果对比

<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>

image.png

2.2.5 虚线模式:setLineDash()

通过数组指定虚线的线段与间隙长度(像素)。数组元素依次为线段长、间隙长、线段长、间隙长……重复循环,若要恢复实线,可传入空数组 ctx.setLineDash([])

绘制多条虚线,展示不同数组模式。效果对比

<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 属性用于设置虚线模式的起始偏移量(像素),会改变虚线图案在路径上的起点位置。连续更新该值(递增或递减)可以实现“流动虚线”动画效果。

效果对比:绘制三条相同的虚线,分别设置不同偏移量。

<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>

🚀 下篇预告:形状与路径篇

在下一篇中,你将学到:

  • 如何绘制矩形、多边形、圆形、弧线等基本形状;
  • 路径的深入用法,包括 beginPathclosePath 的正确姿势;
  • 填充与描边的区别,以及 nonzeroevenodd 两种填充规则的原理与效果;
  • arcarcTo 的详细对比与适用场景。

掌握了这些,你就能组合路径创造出复杂图形,为后续的样式与动画打下坚实基础。