前言
在数据可视化(ECharts)、在线文档、H5 游戏等领域,Canvas 都是不可或缺的核心技术。不同于 DOM 操作,Canvas 提供了立即模式的绘图能力。本文将带你系统盘点 Canvas 的核心 API 与实战技巧。
一、 快速上手:环境搭建
Canvas 就像一块画布,而 context 则是你的画笔,Canvas使用方法如下:
- 创建canvas元素,并为其设置width和height
- 通过id查找到该元素
- 使用
getContext('2d')获取绘制图形的上下文
<canvas id="drawing" width="200" height="200"></canvas>
const drawing = document.getElementById("drawing");
// 严谨起见,先检查浏览器是否支持 getContext
if (drawing.getContext) {
const context = drawing.getContext("2d");
console.log("Canvas 上下文获取成功");
}
二、 基础图形:矩形绘制
矩形是 Canvas 中唯一原生支持的形状,其他图形都需要通过路径组合。
| 方法 | 描述 |
|---|---|
fillRect(x, y, w, h) | 绘制填充矩形(实心) |
strokeRect(x, y, w, h) | 绘制描边矩形(空心) |
clearRect(x, y, w, h) | 清除指定区域(橡皮擦效果) |
-
这三种方法都接受4个参数:
x代表绘制矩形的起点横坐标y代表绘制矩形的起点纵坐标(坐标轴是向下的)w代表绘制矩形的宽度(从x位置向右延升w距离)h代表会在矩形的高度(从y位置向下延升h距离)
-
fillRect绘制出来的矩形,可使用
fillStyle('颜色值')可以给矩形填充颜色 -
storkeRect绘制出来的矩形,可使用
strokeStyle('颜色值')可以给矩形绘制轮廓
const context = drawing.getContext("2d");
// 1. 填充绿色矩形
context.fillStyle = "green";
context.fillRect(10, 10, 50, 50);
// 2. 红色边框矩形
context.strokeStyle = "red";
context.lineWidth = 2; // 设置线宽
context.strokeRect(70, 10, 50, 50);
// 3. 橡皮擦:擦除中间一部分
context.clearRect(15, 15, 20, 20);
三、 路径的艺术:绘制任意形状
路径是 Canvas 的灵魂。记住核心流程:开始路径 -> 移动画笔 -> 绘制线条 -> 闭合/描边。
1. 核心 API
beginPath(): 清空当前路径列表,开始新路径(防止之前的路径被重复绘制)。moveTo(x, y): 移动画笔,只把绘制起始点移动到(x, y)。lineTo(x, y): 绘制一条从上个结束点到(x, y)的直线。arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标(x, y)为圆心,以radius为半径绘制一条弧线,起始角度为startAngle,结束角度为endAngle,counterclockwise表示是否逆时针计算起始角度和结束角度(默认为顺时针)arcTo(x1, y1, x2, y2, radius):绘制从起始点P0到P1(x1,y1)的一条连线,截止绘制从P1(x1,y1)到P2(x2,y2)的连线,接着将这两条线当做切线绘制一个半径为radius的圆弧。(当圆弧过大时,会取两条切线的延长线)使用较少
2. 实战:绘制切线圆弧 (arcTo)
<canvas id="drawing" width="600" height="600" style="border: 1px solid aqua"></canvas>
let drawing = document.getElementById('drawing')
if (drawing.getContext) {
let context = drawing.getContext('2d')
const p0 = { x: 50, y: 50 } // 起点
const p1 = { x: 150, y: 100 } // 控制点1
const p2 = { x: 250, y: 50 } // 控制点2
const radius =100 // 圆角半径
context.beginPath()
context.moveTo(p0.x, p0.y) // 定位起点
context.arcTo(p1.x, p1.y, p2.x, p2.y, radius) // 绘制圆弧
// context.lineTo(p2.x, p2.y) // 连接到终点
context.stroke() // 描边
}1.y, p2.x, p2.y, radius);
context.stroke();
3. 实战:绘制时钟表盘
let drawing = document.getElementById('drawing')
if (drawing.getContext) {
let context = drawing.getContext('2d')
// 创建路径
context.beginPath()
// 绘制外圆
context.arc(100, 100, 99, 0, 2 * Math.PI, false)
// 绘制内圆
context.moveTo(194, 100)
context.arc(100, 100, 94, 0, 2 * Math.PI, false)
// 绘制分针
context.moveTo(100, 100)
context.lineTo(100, 15)
// 绘制时针
context.moveTo(100, 100)
context.lineTo(35, 100)
// 描画路径
context.stroke()
}
四、 绘制文本
Canvas 文本也是图像的一部分,无法像 DOM 文本那样选中。
| 属性/方法 | 描述 |
|---|---|
font | 设置字体,如 "bold 20px Arial" |
textAlign | 水平对齐:start, end, left, right, center |
textBaseline | 垂直对齐:top, middle, bottom |
fillText(text, x, y, maxWidth) | 绘制实心文本,text为文本内容(字符串),xy为文本坐标,maxwidth为可选参数表示最大宽度 |
strokeText(text, x, y,maxWidth) | 绘制空心文本,参数同fillText,同fillText一起调用可实现填充+描边效果 |
context.font = '20px Arial';
context.textAlign = 'center';
// 实心字
context.fillStyle = 'blue';
context.fillText('Combined', 150, 150);
// 空心描边字(重叠效果)
context.strokeStyle = 'red';
context.strokeText('Combined', 150, 150);
五、 图像处理与像素操作
1. 绘制图片 (drawImage)
注意:必须等待图片加载完成后才能绘制,否则画布是空的。
const img = new Image();
img.src = 'test.jpg';
img.onload = function() {
// 参数:图片对象, x, y, width, height
// 还可以传入9个参数实现裁剪:drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)
context.drawImage(img, 0, 0, 200, 200);
}
2. 像素级操作 (ImageData)
这是 Canvas 最强大的功能,可用于滤镜、取色器等。
getImageData(x, y, w, h):获取像素数据,返回值为ImageData的实例,包含三个属性width、height和data,其data属性是包含图像的原始像素信息的数组,每 4 个值代表一个像素 。putImageData(imageData, x, y):将图像数据再绘制到画布上,ImageData:为ImageData实例,w,h表示绘制图像的宽高。
跨域警告:如果绘制的图片跨域且未开启 CORS,调用
getImageData会报错(画布被污染)。
六、 图像合成 (Composite)
决定了当两个图形重叠时,谁显示、谁隐藏,或者颜色如何混合。
globalAlpha:全局透明度 。globalCompositeOperation:该属性定义新图形与画布已有内容的像素混合规则,其取值如下:
| 属性值 (Value) | 效果描述 | 典型场景 |
|---|---|---|
source-over (默认) | 新图形覆盖在旧图形上方 | 最普通的绘图模式 |
source-in | 仅显示新图形与旧图形重叠的部分(显示新图形内容) | 裁剪图形(如:将图片裁成圆形头像) |
source-out | 仅显示新图形与旧图形不重叠的部分 | 镂空效果、反向遮罩 |
destination-over | 新图形绘制在旧图形下方 | 背景叠加(如:给已有文字加底色) |
destination-out | 擦除旧图形中与新图形重叠的部分 | 橡皮擦功能实现 |
lighter | 重叠区域颜色值相加(变亮效果) | 制作光效、粒子系统、火焰效果 |
经典案例:刮刮乐效果 (destination-out)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
/* 容器设置:保证文字和Canvas重叠 */
.scratch-card {
position: relative;
width: 300px;
height: 150px;
margin: 20px auto;
user-select: none; /* 禁止选中文字 */
}
/* 底层的中奖文字 */
.prize-text {
position: absolute;
width: 100%;
height: 100%;
line-height: 150px;
text-align: center;
font-size: 40px;
color: red;
font-weight: bold;
background-color: #f9f9f9;
border: 1px solid #ccc;
z-index: 1; /* 层级较低 */
}
/* 上层的 Canvas 遮罩 */
canvas {
position: absolute;
top: 0;
left: 0;
z-index: 2; /* 层级较高,盖住文字 */
cursor: pointer;
}
</style>
</head>
<body>
<div class="scratch-card">
<div class="prize-text">🎉 中奖了!</div>
<canvas id="mask" width="300" height="150"></canvas>
</div>
<script>
const canvas = document.getElementById('mask');
const ctx = canvas.getContext('2d');
// 1. 初始化:填充灰色遮罩
ctx.fillStyle = '#cccccc'; // 刮奖区的颜色
ctx.fillRect(0, 0, 300, 150);
// 状态标记:是否正在按下鼠标
let isDrawing = false;
// 2. 鼠标/触摸交互事件监听
canvas.addEventListener('mousedown', () => isDrawing = true);
canvas.addEventListener('mouseup', () => isDrawing = false);
// 鼠标移出画布也停止刮奖
canvas.addEventListener('mouseleave', () => isDrawing = false);
canvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
// 获取鼠标在 Canvas 中的坐标
// e.offsetX / e.offsetY 是相对于事件源元素的坐标
const x = e.offsetX;
const y = e.offsetY;
// --- 核心代码开始 ---
// 设置混合模式为“擦除”(即:让重叠部分变透明)
ctx.globalCompositeOperation = 'destination-out';
// 绘制圆形作为“笔触”(圆形比矩形手感更好)
ctx.beginPath();
ctx.arc(x, y, 15, 0, Math.PI * 2); // 15是半径,控制橡皮擦大小
ctx.fill();
// --- 核心代码结束 ---
});
</script>
</body>
</html>