Canvas 绘图初体验:用代码在网页上“画画”的魔法

Canvas 绘图初体验:用代码在网页上"画画"的魔法

如果你写过几年前端,大概率对 DOM 操作、样式布局、组件封装这些事已经驾轻就熟。但有没有想过------浏览器其实还藏着一块"画板" ,你可以像画家一样,用代码一笔一笔地把图形画上去?

这就是 HTML5 的 Canvas

今天这篇文章,我想带你从零开始,亲手在网页上"画"出第一个图形,感受一下用代码作画的奇妙体验。


一、Canvas 是什么?

简单来说,Canvas 是 HTML5 提供的一个画布元素,你可以在上面用 JavaScript 绘制图形、文字、图片,甚至做出动画和游戏。

它和 SVG 最大的区别在于:

Canvas SVG
绘制方式 逐像素渲染(位图) 矢量描述(DOM 节点)
适合场景 游戏、数据可视化、图像处理 图标、简单交互图形
缩放表现 放大可能模糊 无损缩放
交互 需要手动计算坐标 原生支持事件

一句话概括:Canvas 是画笔画出来的,SVG 是用文字描述出来的。


二、准备工作:三行代码搭好画板

先来一个最简单的 HTML 结构:

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Canvas 初体验</title>
  <style>
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      margin: 0;
      background: #1a1a2e;
    }
    canvas {
      border: 2px solid #e2e2e2;
      border-radius: 8px;
    }
  </style>
</head>
<body>
  <canvas id="myCanvas" width="600" height="400"></canvas>
  <script src="main.js"></script>
</body>
</html>

关键点就两个:

  1. <canvas> 标签本身只是个容器,真正画画靠 JS
  2. widthheight 建议直接写在标签属性上(而不是 CSS 里),否则画面会被拉伸

三、拿到画笔:获取绘图上下文

main.js 里,第一步永远是获取 绘图上下文(context)

ini 复制代码
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

getContext('2d') 返回的是一个 CanvasRenderingContext2D ​ 对象------你可以把它理解为画笔 + 调色盘 + 尺子的集合体,后面所有的绘制操作都靠它。


四、画一条线:你的第一行"绘画代码"

来画一条从画布左上角到右下角的对角线:

ini 复制代码
// 1. 把笔移到起点
ctx.moveTo(50, 50);

// 2. 画到终点
ctx.lineTo(550, 350);

// 3. 设置线条颜色
ctx.strokeStyle = '#00d4ff';

// 4. 设置线条粗细
ctx.lineWidth = 3;

// 5. 描边(真正画出来)
ctx.stroke();

刷新页面,你应该能看到一条亮蓝色的斜线横跨画布 ✨

五个步骤,缺一不可:

scss 复制代码
moveTo → lineTo → strokeStyle → lineWidth → stroke()

这就像真实画画一样:先确定起点,再确定终点,选好颜色,调好笔刷粗细,最后落笔。


五、画一个矩形:fill 和 stroke 的区别

Canvas 里有两种"画"法:

  • stroke(描边) :只画轮廓线
  • fill(填充) :把形状内部填满
ini 复制代码
// 描边矩形
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 2;
ctx.strokeRect(100, 80, 150, 100);

// 填充矩形
ctx.fillStyle = 'rgba(78, 205, 196, 0.7)';
ctx.fillRect(300, 80, 150, 100);

strokeRect(x, y, width, height)fillRect(x, y, width, height) 是快捷方法,不需要再调用 stroke()fill()


六、画一个圆:arc 方法

圆在 Canvas 里用 arc(圆弧)来画:

ini 复制代码
ctx.beginPath();
ctx.arc(300, 250, 60, 0, Math.PI * 2);
ctx.fillStyle = '#f7dc6f';
ctx.fill();
ctx.strokeStyle = '#f39c12';
ctx.lineWidth = 3;
ctx.stroke();

arc(x, y, radius, startAngle, endAngle) 的参数含义:

参数 含义
x, y 圆心坐标
radius 半径
startAngle 起始角度(弧度)
endAngle 结束角度(弧度)

💡 注意 :角度用弧度 而不是度数。Math.PI * 2 就是一个完整的圆(360°)。

还有一点很重要:每次画新图形前,记得调用 beginPath() ,否则新旧路径会混在一起,描边时会全部重画一遍。


七、写一段文字

Canvas 也能写字:

ini 复制代码
ctx.font = 'bold 28px "Microsoft YaHei", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', 300, 200);

fillText(text, x, y) 在指定位置绘制填充文字。对应的还有 strokeText() 只画文字轮廓。


八、来点动效:让圆动起来

静态的画布已经很有趣了,但 Canvas 真正的魅力在于动态绘制。来做一个简单的弹球动画:

ini 复制代码
let x = 300;
let y = 200;
let dx = 3;
let dy = 2;
let radius = 20;

function animate() {
  // 清空画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 画圆
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fillStyle = '#e74c3c';
  ctx.fill();

  // 更新位置
  x += dx;
  y += dy;

  // 碰壁反弹
  if (x + radius > canvas.width || x - radius < 0) dx = -dx;
  if (y + radius > canvas.height || y - radius < 0) dy = -dy;

  requestAnimationFrame(animate);
}

animate();

核心思路很简单:

  1. 每一帧清空画布clearRect()
  2. 重新绘制 → 在新位置画圆
  3. 循环requestAnimationFrame()

这就是 Canvas 动画的本质:擦掉重画,擦掉重画,擦掉重画......


九、完整示例代码

把上面所有内容整合在一起,你的 main.js 最终大概长这样:

ini 复制代码
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 画对角线
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(550, 350);
ctx.strokeStyle = '#00d4ff';
ctx.lineWidth = 3;
ctx.stroke();

// 画矩形
ctx.strokeStyle = '#ff6b6b';
ctx.lineWidth = 2;
ctx.strokeRect(100, 80, 150, 100);

ctx.fillStyle = 'rgba(78, 205, 196, 0.7)';
ctx.fillRect(300, 80, 150, 100);

// 画圆
ctx.beginPath();
ctx.arc(300, 250, 60, 0, Math.PI * 2);
ctx.fillStyle = '#f7dc6f';
ctx.fill();
ctx.strokeStyle = '#f39c12';
ctx.lineWidth = 3;
ctx.stroke();

// 写文字
ctx.font = 'bold 28px "Microsoft YaHei", sans-serif';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', 300, 200);

// 弹球动画
let x = 300, y = 200, dx = 3, dy = 2, radius = 20;

function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fillStyle = '#e74c3c';
  ctx.fill();
  x += dx;
  y += dy;
  if (x + radius > canvas.width || x - radius < 0) dx = -dx;
  if (y + radius > canvas.height || y - radius < 0) dy = -dy;
  requestAnimationFrame(animate);
}
animate();

十、写在最后

第一次接触 Canvas 的时候,我最大的感受是:它太"底层"了。

没有组件、没有声明式语法、没有响应式更新------你就是那个画家,每一个像素都要自己决定。画线要自己算坐标,动画要自己擦了重画,交互要自己监听鼠标位置判断命中。

但正是这种"底层",给了它无限的可能性。ECharts、AntV 这些数据可视化库底层都是 Canvas;网页游戏、图片编辑器、签名板、粒子特效......背后都有它的身影。

Canvas 的魔法不在于它有多复杂,而在于它足够简单直接------给你一块画布、一支笔,剩下的全凭你的想象力。

相关推荐
长大198817 分钟前
旧版浏览器不兼容?一行代码引入 html5shiv 拯救你的页面
后端
大黄评测17 分钟前
图文排版神器:用 `<figure>` 和 `<figcaption>` 规范你的内容结构
后端
JavaGuide2 小时前
SpaceX 工程师的 AI Coding 玩法太牛了, 200 多个 Agent 并行!
前端·后端
SQL-First布道者2 小时前
⚡Spring JDBC 完整体系 · 第 10 集 · 手搓 `BaseCondition`
java·spring boot·后端·spring·mybatis·spring jdbc
2601_962074682 小时前
Spring Boot的无缝衔接:深入解析与实践
数据库·spring boot·后端
聪明蛋子哟3 小时前
多智能体协作的三种模式:从群聊到分层,如何设计可扩展的Agent系统
后端·python·flask
DolphinDB3 小时前
告别手写代码:Trae + DolphinDB Skill,10 分钟造一个懂期货的投研 Agent
后端
ChenNyan3 小时前
从一块 ESP32-S3 开发板到 AI 陪伴设备:我们如何搭建一套可持续演进的端云架构
后端
ValueHD3 小时前
视频会议终端是什么,有哪几种类型?
后端·音视频·视频编解码·腾讯会议