JavaScript调用HTML5 Canvas API绘制2D图形教程

作者:竹影

时间:2026-10-30

浏览:469

本教程详细讲解如何使用 JavaScript 调用 HTML5 Canvas API 进行 2D 图形绘制,涵盖画布准备、坐标系原理、基础图元与路径绘制、画笔样式配置、状态栈管理及动画重绘循环等关键知识。

文章目录

假设你正在开发一个网页功能,需要动态生成折线统计图、带有圆角的自定义徽章,或者一个交互式涂鸦画板。使用传统的 HTML 标签和 CSS 样式来处理这些连续、不规则且动态变化的图形,往往会带来庞大的 DOM 节点开销和严重的性能抖动。HTML5 引入的 <canvas> 元素与配套的 2D 渲染上下文(CanvasRenderingContext2D),正是专为网页即时像素绘制而设计的原生技术方案。本文将以直接、务实的步骤,全面讲解如何通过 JavaScript 获取画布上下文、掌握二维坐标系统、绘制基础矩形与复杂矢量路径、配置填充和描边样式,以及利用状态栈与动画循环实现平滑的动态重绘。

一、准备画布与获取 2D 渲染上下文

使用 Canvas 进行绘制的第一步是在 HTML 结构中声明一个 <canvas> 标签。Canvas 元素本身仅是一块透明的光栅画板,它不具备主动绘图能力,全部画面都必须通过 JavaScript 调用其提供的绘图上下文 API 来呈现。

在声明 <canvas> 标签时,最核心的前提是直接在标签属性中明确指定 width 和 height:

<!-- index.html -->
<canvas id="stage" width="800" height="500">
  您的浏览器不支持 HTML5 Canvas,请升级现代浏览器后访问。
</canvas>

标签内嵌套的文字属于后备内容。现代浏览器支持 Canvas 时会忽略其中的文字并渲染画布;如果用户使用的是老旧浏览器,则会展示该提示。在 JavaScript 中,我们通过 DOM 获取该元素,并调用 getContext('2d') 初始化 2D 绘图环境:

// script.js
const canvas = document.getElementById('stage');

// 健壮性检查:验证浏览器是否支持 getContext 方法
if (canvas && canvas.getContext) {
  const ctx = canvas.getContext('2d');
  // 获取到的 ctx 即为 CanvasRenderingContext2D 实例,后续所有绘制均由其驱动
} else {
  console.warn('当前运行环境不支持 HTML5 Canvas');
}
特别注意画布尺寸与 CSS 样式的区别:
<canvas> 具有两套尺寸概念。标签属性中的 width 与 height 决定了画布内部物理像素缓冲区的真实分辨率(默认值为 300 × 150 像素)。如果通过 CSS 样式(如 style="width: 800px; height: 500px;")来调整尺寸,浏览器仅会对原本 300 × 150 的像素画面进行拉伸缩放,从而导致线条模糊变粗和像素失真。因此,应始终优先在 HTML 属性中设定画布尺寸。
JavaScript调用HTML5 Canvas API绘制2D图形教程
图 1:HTML5 Canvas 2D 绘图的核心四步执行链条

二、理解 2D 坐标系与基础矩形绘制

在向画布绘制任何内容之前,必须理解 Canvas 的二维坐标系统:

  • 坐标原点 (0, 0):位于画布的左上角,而不是传统数学笛卡尔坐标系的左下角。
  • X 轴正方向:水平向右递增,取值范围为 0 到 canvas.width。
  • Y 轴正方向:垂直向下递增,取值范围为 0 到 canvas.height。

矩形(Rectangle)是 Canvas API 中唯一可以直接一步调用的原生独立图元(无需手动经历 beginPath 和 closePath 路径构建)。Canvas 提供了三个专门用于操作矩形区域的方法:

// 1. 填充矩形:在坐标 (50, 40) 处绘制一个宽 200、高 100 像素的实心矩形
ctx.fillStyle = '#83ADD0';
ctx.fillRect(50, 40, 200, 100);

// 2. 描边矩形:在坐标 (300, 40) 处绘制一个宽 200、高 100 像素的空心边框矩形
ctx.strokeStyle = '#77B9AE';
ctx.lineWidth = 3;
ctx.strokeRect(300, 40, 200, 100);

// 3. 清除矩形区域:在已填充的矩形中央擦除一个 80x40 的完全透明区域
ctx.clearRect(110, 70, 80, 40);
JavaScript调用HTML5 Canvas API绘制2D图形教程
图 2:Canvas 二维坐标轴及属性分辨率映射机制

三、使用路径(Path)绘制多边形、弧线与圆形

除了简单的矩形,其他所有的几何形状(线段、折线、多边形、圆弧、贝塞尔曲线等)都必须依托“路径”(Path)机制来构建。Canvas 的路径绘制模型可以形象地理解为“拿笔在纸上规划草图轨迹”,轨迹绘制完毕后,再统一调用描边(stroke)或填充(fill)指令将颜色沉淀到画布上。

1. 路径的基本生命周期

任何路径绘制都应遵循以下规范步骤:

  1. ctx.beginPath():开启一条新路径,清空当前上下文的子路径列表。
  2. ctx.moveTo(x, y):将虚拟笔尖移动到指定起点,不产生可见线条。
  3. ctx.lineTo(x, y):从当前位置画一条直线连接到目标坐标。
  4. ctx.closePath()(可选):从当前落笔点向子路径起点绘制一条封闭直线。
  5. ctx.stroke() 或 ctx.fill():执行实际渲染。stroke() 会绘制路径边框,fill() 会填充封闭区域。
// 示例:绘制一个带有填充和描边的多边形三角形
ctx.beginPath();
ctx.moveTo(100, 50);    // 顶点 A
ctx.lineTo(180, 180);   // 顶点 B
ctx.lineTo(20, 180);    // 顶点 C
ctx.closePath();        // 自动连线回到 A,闭合三角形

ctx.fillStyle = '#83ADD0';
ctx.fill();             // 填充内部

ctx.lineWidth = 4;
ctx.strokeStyle = '#142537';
ctx.stroke();           // 勾画边框

2. 绘制圆弧与完整圆形

绘制圆形或圆弧使用 arc() 方法,其语法签名为:
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)

  • x, y:圆心坐标。
  • radius:圆的半径。
  • startAngle, endAngle:起始角与结束角。必须使用弧度(Radian)而非角度值。角度转弧度公式为:弧度 = 角度 * (Math.PI / 180)。完整圆形对应的弧度为 0 到 Math.PI * 2。
  • anticlockwise:可选布尔值,true 表示逆时针绘制,默认 false(顺时针)。
// 示例:在 (400, 150) 绘制一个半径为 60 的完整圆形
ctx.beginPath();
ctx.arc(400, 150, 60, 0, Math.PI * 2, false);
ctx.fillStyle = '#77B9AE';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = '#142537';
ctx.stroke();
核心避坑准则:切勿遗漏 beginPath()
每次绘制独立的图形前,必须显式调用 ctx.beginPath()。如果不调用该方法,之前的所有路径信息依然保存在内存中,后续调用 stroke() 或 fill() 时,旧路径会带着新的样式重复绘制,不仅造成逻辑混乱,还会引发严重的渲染性能问题。
JavaScript调用HTML5 Canvas API绘制2D图形教程
图 3:CanvasRenderingContext2D 常用核心 API 结构全景

四、样式属性配置、文本渲染与状态栈管理

Canvas 2D 上下文在底层本质上是一个状态机(State Machine)。当你对 fillStyle 或 lineWidth 进行赋值后,该设置会一直生效并作用于后续的所有绘制操作,直到你再次更改它。

1. 常用画笔样式与文本配置

属性名 默认值 说明与取值范例
fillStyle #000000 图形内部填充颜色,支持十六进制、rgb()、rgba()
strokeStyle #000000 图形边框描边颜色
lineWidth 1.0 边框线条粗细(像素数值)
lineCap butt 线段端点样式:butt(平头)、round(圆头)、square(方头)
font 10px sans-serif 文本字体设置,语法与 CSS font 简写完全相同
textAlign start 水平对齐方式:start、end、left、right、center

2. 状态保护:save() 与 restore()

在大型应用或模块化绘图中,某个组件在绘制自身时往往需要临时修改全局样式或对坐标系执行位移(translate)、旋转(rotate)。为了避免这些局部修改污染全局环境,必须成对使用 ctx.save() 与 ctx.restore():

  • ctx.save():将当前的全部样式配置、变换矩阵和裁剪区域压入内部状态栈。
  • ctx.restore():从栈顶弹出最近一次保存的配置,将上下文恢复至压栈时的状态。
function drawBadge(ctx, text, x, y) {
  // 1. 保存当前全局绘图状态
  ctx.save();

  // 2. 局部环境配置
  ctx.fillStyle = '#142537';
  ctx.font = '16px "PingFang SC", sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';

  // 3. 绘制局部内容
  ctx.fillText(text, x, y);

  // 4. 彻底恢复原有状态,不影响后续代码
  ctx.restore();
}

五、构建平滑动画:requestAnimationFrame 循环重绘

静态图表往往不足以满足生动的交互需求。Canvas 本身没有像 DOM 元素那样的“图形对象引用”,一旦图形被光栅化沉淀为像素,画布便不再知道那里存在一个“圆”或“矩形”。因此,Canvas 实现动画的唯一途径就是“擦除旧帧,重新绘制新帧”。

现代 JavaScript 中实现动画的标准手段是浏览器提供的 window.requestAnimationFrame。它能确保回调函数在屏幕刷新率的精确时间节点(通常为 60Hz 即每 16.6ms 一次)触发,杜绝了 setInterval 引发的掉帧与撕裂:

// 完整动画示例:小球在边界内水平反弹
const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');

let ball = {
  x: 50,
  y: 120,
  radius: 20,
  speedX: 4
};

function renderLoop() {
  // 1. 清除整张画布上一帧的残余像素
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 2. 更新物体状态与边界检测
  ball.x += ball.speedX;
  if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
    ball.speedX = -ball.speedX; // 触碰画布左右边界时反向
  }

  // 3. 绘制当前帧的物体
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
  ctx.fillStyle = '#77B9AE';
  ctx.fill();

  // 4. 递归注册下一帧渲染
  requestAnimationFrame(renderLoop);
}

// 启动动画循环
requestAnimationFrame(renderLoop);

六、工程实践中的常见问题与优化方案

1. 高清屏(Retina / 高 DPI 屏幕)下画面与文字模糊

在手机或 Mac 等高分屏设备上,物理像素数通常是 CSS 逻辑像素数的 2 倍或 3 倍(通过 window.devicePixelRatio 获取)。如果画布的尺寸仅按 CSS 像素声明,1 个 Canvas 像素会被放大拉伸映射到 4 个或 9 个物理像素上,从而显得极为模糊。标准化解决方案如下:

function initHighDPICanvas(canvas, logicalWidth, logicalHeight) {
  const dpr = window.devicePixelRatio || 1;

  // 1. 设置物理像素缓冲区为真实尺寸
  canvas.width = logicalWidth * dpr;
  canvas.height = logicalHeight * dpr;

  // 2. 通过 CSS 样式维持其在页面上的逻辑显示尺寸
  canvas.style.width = logicalWidth + 'px';
  canvas.style.height = logicalHeight + 'px';

  // 3. 将上下文等比放大,后续开发完全继续使用原逻辑坐标系统
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
  return ctx;
}

2. 1 像素线条发虚变粗变成 2 像素

Canvas 绘制线条时,是以坐标中线为基准向两侧各延伸一半线宽。如果你在整数坐标 x = 100 处绘制 lineWidth = 1 的垂直线,线条会落在 99.5 到 100.5 像素区间内,导致浏览器对两侧两列物理像素执行半透明抗锯齿采样,肉眼看起来就是一根发虚变浅的 2 像素线条。解决该问题只需将整数坐标偏移 0.5 像素(例如 ctx.moveTo(100.5, 0); ctx.lineTo(100.5, 300);),使线条精准覆盖在物理像素栅格之内。

JavaScript调用HTML5 Canvas API绘制2D图形教程
图 4:Canvas 2D 图形开发闭环与工程规范总结

通过遵循本教程所梳理的“声明画布容器 → 初始化 2D 上下文 → 规划路径图元 → 注入样式与状态防护 → 闭环动画调度”五大核心阶段,你便能够系统性地驾驭 HTML5 Canvas 2D 图形绘制,在保持页面高渲染性能的同时,实现各种精致复杂的视觉表现与动态效果。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章