Canvas 图形绘制与高级功能
一、Canvas 核心绘制基础
在完成 Canvas 容器初始化(获取元素、设置宽高、获取 2D 上下文)后,核心绘制逻辑围绕 “路径定义→样式设置→执行绘制” 三步展开,支持直线、多边形、圆形等多种基础形状。
1. 基础路径与绘制原则
Canvas 是路径式绘图,需先通过 moveTo()
/lineTo()
等方法定义路径,再通过 stroke()
(描边)或 fill()
(填充)执行绘制。关键注意点:
- 多个独立图形需用
beginPath()
开启新路径、closePath()
关闭路径,避免样式 / 路径相互影响。 - 样式属性(如
lineWidth
、fillStyle
)需在绘制前设置,否则不生效。
2. 基础形状绘制实战
(1)绘制直线
通过 moveTo(x1,y1)
定义起点,lineTo(x2,y2)
定义终点,stroke()
描边显示。支持设置线条宽度、颜色、端点样式。
javascript
运行
window.onload = function() {var canvas = document.getElementById('canvas');canvas.width = 800;canvas.height = 600;var ctx = canvas.getContext('2d');// 1. 开启新路径ctx.beginPath();// 2. 定义路径:起点(100,100) → 终点(700,500)ctx.moveTo(100, 100); ctx.lineTo(700, 500);// 3. 设置线条样式ctx.lineWidth = 5; // 线条宽度(像素)ctx.strokeStyle = '#ff4400'; // 线条颜色ctx.lineCap = 'round'; // 端点样式:round(圆角)、butt(平角,默认)、square(方角)// 4. 执行描边ctx.stroke();// 5. 关闭路径(可选,直线可省略)ctx.closePath();
};
(2)绘制多边形(三角形、正方形)
多边形本质是 “多段直线连接的闭合路径”,需将最后一个点与起点连接(可手动 lineTo(起点)
或用 closePath()
自动闭合)。
示例 1:带填充色的三角形
javascript
运行
ctx.beginPath();
// 定义三角形三个顶点
ctx.moveTo(100, 100);
ctx.lineTo(100, 300);
ctx.lineTo(300, 300);
ctx.closePath(); // 自动闭合路径(连接(300,300)→(100,100))// 样式设置
ctx.lineWidth = 3;
ctx.strokeStyle = 'blue'; // 边框颜色
ctx.fillStyle = 'pink'; // 填充颜色// 执行绘制(先描边后填充,顺序不影响结果)
ctx.stroke();
ctx.fill();
示例 2:循环绘制多个正方形(带偏移)
通过循环动态生成坐标,实现多个正方形的批量绘制:
javascript
运行
var colors = ['green', 'blue', 'pink', 'yellow', 'black']; // 填充色数组for (var i = 0; i < colors.length; i++) {var offset = i * 20; // 每个正方形的偏移量(避免重叠)ctx.beginPath();// 正方形顶点:(100+offset,100+offset) → (400+offset,100+offset) → (400+offset,400+offset) → (100+offset,400+offset)ctx.rect(100 + offset, 100 + offset, 300, 300); // 简化写法:rect(x,y,宽,高) 直接定义矩形路径ctx.fillStyle = colors[i];ctx.strokeStyle = 'red';ctx.fill();ctx.stroke();ctx.closePath();
}
(3)绘制圆形与圆弧(arc()
方法)
圆形是 “360° 的圆弧”,核心依赖 arc(centerX, centerY, radius, startAngle, endAngle, anticlockwise)
方法,需理解弧度与角度的转换(1° = Math.PI/180
弧度)。
参数 | 说明 |
---|---|
centerX /centerY | 圆心坐标(x/y 轴) |
radius | 圆的半径(像素) |
startAngle | 起始弧度(0 = 3 点钟方向) |
endAngle | 结束弧度(2π = 360°) |
anticlockwise | 布尔值,true = 逆时针,false = 顺时针(默认) |
示例 1:实心圆与空心圆
javascript
运行
// 1. 实心圆(填充+无描边)
ctx.beginPath();
ctx.arc(300, 300, 100, 0, 2 * Math.PI); // 0→2π 即360°
ctx.fillStyle = 'red';
ctx.fill();
ctx.closePath();// 2. 空心圆(描边+无填充)
ctx.beginPath();
ctx.arc(550, 300, 100, 0, 2 * Math.PI);
ctx.lineWidth = 5;
ctx.strokeStyle = 'blue';
ctx.stroke();
ctx.closePath();
示例 2:半圆与扇形
- 半圆:结束弧度设为
Math.PI
(180°); - 扇形:需先将起点移到圆心(
moveTo(centerX, centerY)
),再绘制圆弧,形成 “扇形闭合路径”。
javascript
运行
// 半圆(上半圆:从π→2π,逆时针)
ctx.beginPath();
ctx.arc(300, 300, 100, Math.PI, 2 * Math.PI, true);
ctx.strokeStyle = 'orange';
ctx.lineWidth = 3;
ctx.stroke();
ctx.closePath();// 扇形(90°:从-π/2→0,顺时针)
ctx.beginPath();
ctx.moveTo(550, 300); // 起点=圆心
ctx.arc(550, 300, 100, -Math.PI/2, 0); // -π/2=12点钟方向,0=3点钟方向
ctx.closePath(); // 自动连接圆弧终点→圆心
ctx.fillStyle = 'purple';
ctx.fill();
(4)绘制椭圆(ellipse()
方法)
椭圆是 “x 轴 /y 轴半径不同的圆”,通过 ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
实现,支持旋转效果。
javascript
运行
ctx.beginPath();
// 椭圆:圆心(400,400),x轴半径300,y轴半径200,旋转0°,360°闭合
ctx.ellipse(400, 400, 300, 200, 0, 0, 2 * Math.PI);
ctx.fillStyle = '#058'; // 填充色
ctx.strokeStyle = '#000'; // 描边色
ctx.fill();
ctx.stroke();
二、文字绘制与样式
Canvas 支持两种文字绘制模式:fillText()
(填充文字)和 strokeText()
(镂空文字),可设置字体、大小、阴影、渐变等样式。
1. 基础文字绘制
javascript
运行
var canvas = document.getElementById('canvas');
canvas.width = 800;
canvas.height = 400;
var ctx = canvas.getContext('2d');var text = 'Canvas 文字绘制';
// 1. 设置字体样式:font = "字体大小 字体家族"(顺序不可乱)
ctx.font = '48px 微软雅黑'; // 2. 填充文字(实心)
ctx.fillStyle = 'red';
ctx.fillText(text, 100, 100); // 参数:文字内容,x(起始x),y(文字基线y)// 3. 镂空文字(描边)
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.strokeText(text, 100, 200);
2. 文字高级样式
(1)文字阴影
通过 shadowColor
(阴影色)、shadowOffsetX
(水平偏移)、shadowOffsetY
(垂直偏移)、shadowBlur
(模糊程度)实现阴影效果。
javascript
运行
ctx.font = '48px 微软雅黑';
ctx.fillStyle = 'green';
// 阴影配置:红色阴影,水平偏移4px,垂直偏移4px,模糊40px
ctx.shadowColor = 'red';
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowBlur = 40;
ctx.fillText('带阴影的文字', 100, 150);
(2)文字渐变
需先通过 createLinearGradient(x0,y0,x1,y1)
创建线性渐变对象,再用 addColorStop(位置, 颜色)
定义渐变节点,最后将渐变对象赋值给 fillStyle
。
javascript
运行
ctx.font = '48px 微软雅黑';
// 1. 创建线性渐变:从(0,200)→(400,200)(水平渐变)
var gradient = ctx.createLinearGradient(0, 200, 400, 200);
// 2. 定义渐变节点:位置0→黑色,0.3→品红,0.5→蓝色,0.8→黄色,1→红色
gradient.addColorStop(0, 'black');
gradient.addColorStop(0.3, 'magenta');
gradient.addColorStop(0.5, 'blue');
gradient.addColorStop(0.8, 'yellow');
gradient.addColorStop(1, 'red');
// 3. 应用渐变到文字
ctx.fillStyle = gradient;
ctx.fillText('渐变文字', 100, 200);
三、曲线绘制(贝塞尔曲线)
Canvas 支持二次贝塞尔曲线(1 个控制点)和三次贝塞尔曲线(2 个控制点),常用于绘制平滑曲线(如波浪、路径)。
1. 二次贝塞尔曲线(quadraticCurveTo()
)
语法:ctx.quadraticCurveTo(cpX, cpY, x, y)
cpX
/cpY
:控制点坐标(决定曲线弯曲方向);x
/y
:曲线终点坐标;- 起点需通过
moveTo()
定义。
javascript
运行
ctx.beginPath();
ctx.moveTo(10, 100); // 起点
// 控制点(200, 20),终点(400, 100)
ctx.quadraticCurveTo(200, 20, 400, 100);
ctx.lineWidth = 3;
ctx.strokeStyle = 'purple';
ctx.stroke();
2. 三次贝塞尔曲线(bezierCurveTo()
)
语法:ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, x, y)
cp1X
/cp1Y
:第一个控制点;cp2X
/cp2Y
:第二个控制点;x
/y
:曲线终点。
javascript
运行
ctx.beginPath();
ctx.moveTo(50, 300); // 起点
// 两个控制点(150, 100)、(350, 500),终点(450, 300)
ctx.bezierCurveTo(150, 100, 350, 500, 450, 300);
ctx.lineWidth = 3;
ctx.strokeStyle = 'orange';
ctx.stroke();
四、Canvas 高级功能
1. 2D 变换(平移、旋转、缩放)
Canvas 通过修改 “坐标系” 实现图形变换,变换会累积影响后续所有绘制,需用 save()
/restore()
管理变换状态(避免影响其他图形)。
(1)平移(translate(x, y)
)
将坐标系原点(默认 (0,0))移动到 (x,y)
,后续绘制的图形会基于新原点偏移。
javascript
运行
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 150, 150); // 原始位置矩形// 平移坐标系:x轴+500,y轴+0
ctx.translate(500, 0);
ctx.beginPath();
ctx.fillStyle = 'green';
ctx.fillRect(100, 100, 150, 150); // 基于新原点的矩形(实际位置:600,100)
(2)旋转(rotate(rad)
)
绕当前坐标系原点旋转指定弧度(顺时针为正),需注意:旋转中心默认是原点,若需绕图形自身中心旋转,需先平移到图形中心,旋转后再平移回原位。
javascript
运行
// 绕原点旋转45°(π/4 弧度)
ctx.beginPath();
ctx.rotate(Math.PI / 4); // 45°旋转
ctx.fillStyle = 'blue';
ctx.fillRect(100, 0, 100, 100); // 旋转后的矩形
(3)缩放(scale(x, y)
)
按比例缩放坐标系:x
为水平缩放比例,y
为垂直缩放比例(<1 缩小,>1 放大,负值反转坐标系)。
javascript
运行
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100); // 原始大小// 缩放:x轴0.5倍,y轴0.5倍(缩小到50%)
ctx.scale(0.5, 0.5);
ctx.beginPath();
ctx.fillStyle = 'green';
ctx.fillRect(200, 200, 100, 100); // 缩放后的矩形(实际大小:50x50,位置:100,100)
(4)状态管理(save()
/restore()
)
save()
:保存当前 Canvas 状态(包括变换、样式、裁剪区域);restore()
:恢复到最近一次save()
保存的状态,避免变换累积影响其他图形。
javascript
运行
ctx.save(); // 保存初始状态
// 变换1:平移+旋转
ctx.translate(200, 200);
ctx.rotate(Math.PI / 4);
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 100);
ctx.restore(); // 恢复到初始状态// 不受之前变换影响的新图形
ctx.beginPath();
ctx.fillStyle = 'blue';
ctx.fillRect(300, 300, 100, 100);
2. 图形组合与透明度
(1)全局透明度(globalAlpha
)
设置后续所有绘制的透明度(0 = 完全透明,1 = 完全不透明),会与图形自身透明度(如 rgba()
)叠加。
javascript
运行
ctx.globalAlpha = 0.5; // 全局透明度50%
// 红色矩形(实际透明度:0.5 * 1 = 0.5)
ctx.fillStyle = 'rgba(255,0,0,1)';
ctx.fillRect(100, 100, 200, 100);
// 蓝色矩形(实际透明度:0.5 * 0.8 = 0.4)
ctx.fillStyle = 'rgba(0,0,255,0.8)';
ctx.fillRect(150, 150, 200, 100);
(2)图形组合模式(globalCompositeOperation
)
控制 “新图形(源)” 与 “已有图形(目标)” 的叠加方式,常用模式如下:
模式值 | 效果 |
---|---|
source-over | 源在目标上方(默认) |
destination-over | 目标在源上方 |
source-in | 只显示源与目标的重叠部分(源颜色) |
xor | 只显示源与目标的非重叠部分 |
lighter | 重叠部分颜色相加(更亮) |
javascript
运行
// 1. 绘制目标图形(红色圆)
ctx.beginPath();
ctx.arc(350, 250, 150, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();// 2. 设置组合模式为 "xor"(非重叠部分显示)ctx.globalCompositeOperation = 'xor';// 3. 绘制源图形(绿色圆)ctx.beginPath ();ctx.arc (550, 250, 150, 0, 2 * Math.PI);ctx.fillStyle = 'green';ctx.fill ();### 3. 图片操作(绘制、裁剪、滤镜)
Canvas 支持将图片绘制到画布中,并实现裁剪、缩放、滤镜等效果,核心方法是 `drawImage()`。#### (1)基础图片绘制(三种用法)
`drawImage()` 有三种参数形式,分别对应“直接绘制”“指定大小绘制”“裁剪后绘制”:| 用法 | 语法 | 说明 |
|------|------|------|
| 直接绘制 | `drawImage(img, x, y)` | `img`:图片对象;`x/y`:图片在画布的左上角坐标 |
| 指定大小 | `drawImage(img, x, y, w, h)` | `w/h`:图片在画布的显示宽高(会拉伸) |
| 裁剪绘制 | `drawImage(img, sx, sy, sw, sh, x, y, w, h)` | `sx/sy`:图片裁剪的左上角坐标;`sw/sh`:裁剪区域宽高;`x/y/w/h`:画布显示位置与大小 |```javascript
window.onload = function() {var canvas = document.getElementById('canvas');canvas.width = 800;canvas.height = 600;var ctx = canvas.getContext('2d');// 1. 创建图片对象var img = new Image();img.src = 'img/banner.png'; // 图片路径// 2. 图片加载完成后绘制(必须在 onload 中,避免图片未加载导致绘制失败)img.onload = function() {// 用法1:直接绘制(原图大小,位置(0,0))ctx.drawImage(img, 0, 0);// 用法2:指定大小绘制(缩放到 400x300,位置(400,0))ctx.drawImage(img, 400, 0, 400, 300);// 用法3:裁剪绘制(从图片(0,0)裁剪 200x200,在画布(0,300)显示为 300x300)ctx.drawImage(img, 0, 0, 200, 200, 0, 300, 300, 300);};
};
(2)图片像素操作(反转、灰度、马赛克)
通过 getImageData()
获取图片像素数据(ImageData
对象,含 data
数组,存储 RGBA 像素值),修改像素后用 putImageData()
重新渲染。
示例 1:颜色反转(每个像素的 RGB 值 = 255 - 原始值)
javascript
运行
img.onload = function() {// 1. 绘制原图ctx.drawImage(img, 0, 0, 400, 300);// 2. 获取像素数据(x,y,宽,高)var imageData = ctx.getImageData(0, 0, 400, 300);var data = imageData.data; // 像素数组:[R1, G1, B1, A1, R2, G2, B2, A2, ...]// 3. 遍历修改像素(每4个元素为一个像素的 RGBA)for (var i = 0; i < data.length; i += 4) {data[i] = 255 - data[i]; // R 通道反转data[i + 1] = 255 - data[i + 1]; // G 通道反转data[i + 2] = 255 - data[i + 2]; // B 通道反转// A 通道(透明度)不变}// 4. 重新渲染修改后的像素(位置(400,0))ctx.putImageData(imageData, 400, 0);
};
示例 2:灰度效果(RGB 平均值 = 灰度值)
javascript
运行
for (var i = 0; i < data.length; i += 4) {var gray = (data[i] + data[i + 1] + data[i + 2]) / 3; // 计算灰度值data[i] = gray; // R = 灰度data[i + 1] = gray; // G = 灰度data[i + 2] = gray; // B = 灰度
}
4. 画布清除(两种方式)
(1)局部清除(clearRect(x, y, w, h)
)
清除画布上指定矩形区域的内容,保留其他区域和画布样式(如背景色、已设置的 globalAlpha
)。
javascript
运行
// 绘制一个矩形
ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 200, 200);// 清除矩形内部的 100x100 区域(位置(150,150))
ctx.clearRect(150, 150, 100, 100);
(2)全量清除(重置宽高)
通过重新设置 Canvas 宽高,强制清空画布并重置所有状态(包括变换、样式、像素数据),适用于需要 “完全重置” 的场景。
javascript
运行
// 绘制一个矩形
ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 200, 200);// 重置宽高(清空画布并重置状态)
canvas.width = canvas.width;// 重新绘制新图形(不受之前样式影响)
ctx.fillStyle = 'blue';
ctx.fillRect(150, 150, 100, 100);
五、Canvas 交互与案例
1. 基础交互(鼠标事件)
结合鼠标事件(onmousedown
/onmousemove
/onmouseup
),可实现 “点击绘图”“拖拽图形” 等交互效果。
示例:点击画布添加矩形
javascript
运行
canvas.onclick = function(e) {// 获取鼠标在画布中的坐标(需计算画布偏移量)var rect = canvas.getBoundingClientRect(); // 获取画布在页面中的位置var x = e.clientX - rect.left; // 鼠标在画布的 x 坐标var y = e.clientY - rect.top; // 鼠标在画布的 y 坐标// 在点击位置绘制 50x50 的矩形ctx.beginPath();ctx.fillStyle = 'rgba(0, 128, 255, 0.5)';ctx.fillRect(x - 25, y - 25, 50, 50); // 矩形中心对齐鼠标位置
};
2. 经典案例:刮刮乐效果
原理:在画布上绘制 “覆盖层”(如灰色矩形),监听鼠标拖拽事件,通过 globalCompositeOperation = 'destination-out'
清除拖拽路径,露出下层图片。
javascript
运行
var canvas = document.getElementById('canvas');
canvas.width = 400;
canvas.height = 200;
var ctx = canvas.getContext('2d');
var isDrawing = false;// 1. 绘制下层图片(中奖内容)
var prizeImg = new Image();
prizeImg.src = 'img/prize.png';
prizeImg.onload = function() {ctx.drawImage(prizeImg, 0, 0, 400, 200);// 2. 绘制上层覆盖层(灰色)ctx.fillStyle = '#ccc';ctx.fillRect(0, 0, 400, 200);
};// 3. 鼠标事件:开始拖拽
canvas.onmousedown = function() {isDrawing = true;
};// 4. 鼠标事件:拖拽清除覆盖层
canvas.onmousemove = function(e) {if (!isDrawing) return;var rect = canvas.getBoundingClientRect();var x = e.clientX - rect.left;var y = e.clientY - rect.top;// 设置组合模式:清除目标区域(覆盖层)ctx.globalCompositeOperation = 'destination-out';// 绘制圆形路径(模拟“刮痕”)ctx.beginPath();ctx.arc(x, y, 10, 0, 2 * Math.PI);ctx.fill();
};// 5. 鼠标事件:结束拖拽
canvas.onmouseup = function() {isDrawing = false;
};
3. 经典案例:Canvas 钟表
原理:通过 setInterval
定时重绘,结合角度计算绘制表盘、时针、分针、秒针,实时更新时间。
javascript
运行
function drawClock() {// 1. 清空画布canvas.width = canvas.width;// 2. 获取当前时间var now = new Date();var h = now.getHours() % 12; // 转为12小时制var m = now.getMinutes();var s = now.getSeconds();// 3. 绘制表盘(圆形)ctx.beginPath();ctx.arc(200, 200, 180, 0, 2 * Math.PI);ctx.strokeStyle = '#333';ctx.lineWidth = 5;ctx.stroke();// 4. 绘制刻度(12个大刻度,60个小刻度)for (var i = 0; i < 60; i++) {ctx.save();ctx.translate(200, 200); // 平移到表盘中心ctx.rotate((i * 6) * Math.PI / 180); // 每刻度6°(360/60)ctx.beginPath();ctx.moveTo(0, -180);// 大刻度(每5个刻度):长度20,小刻度:长度10ctx.lineTo(0, i % 5 === 0 ? -160 : -170);ctx.strokeStyle = i % 5 === 0 ? '#f00' : '#999';ctx.lineWidth = i % 5 === 0 ? 3 : 1;ctx.stroke();ctx.restore();}// 5. 绘制时针(每小时30°:360/12,每分钟额外0.5°:30/60)var hRad = (h * 30 + m * 0.5) * Math.PI / 180;drawHand(hRad, 100, 6, '#333'); // 长度100,宽度6,颜色#333// 6. 绘制分针(每分钟6°:360/60,每秒额外0.1°:6/60)var mRad = (m * 6 + s * 0.1) * Math.PI / 180;drawHand(mRad, 140, 4, '#666'); // 长度140,宽度4,颜色#666// 7. 绘制秒针(每秒6°:360/60)var sRad = s * 6 * Math.PI / 180;drawHand(sRad, 160, 2, '#f00'); // 长度160,宽度2,颜色#f00// 绘制时针/分针/秒针的通用函数function drawHand(rad, length, width, color) {ctx.save();ctx.translate(200, 200);ctx.rotate(rad);ctx.beginPath();ctx.moveTo(0, 20); // 指针尾部延伸20pxctx.lineTo(0, -length);ctx.strokeStyle = color;ctx.lineWidth = width;ctx.lineCap = 'round';ctx.stroke();ctx.restore();}
}// 初始化绘制 + 每秒重绘
drawClock();
setInterval(drawClock, 1000);
六、Canvas 性能优化与注意事项
- 减少重绘范围:频繁动画(如游戏、实时图表)时,避免全量清空画布,优先用
clearRect()
清除局部区域。 - 使用离屏 Canvas:复杂图形(如大量粒子、重复元素)可先在 “隐藏的离屏 Canvas” 中绘制,再一次性渲染到可见画布,减少绘制次数。
- 避免频繁样式修改:尽量将相同样式的绘制合并,减少
fillStyle
/lineWidth
等属性的重复设置。 - 合理使用
requestAnimationFrame
:替代setInterval
实现动画,让浏览器自动优化绘制时机(避免掉帧)。 - 注意像素数据安全:
getImageData()
受同源策略限制,跨域图片需设置crossOrigin
属性,否则无法获取像素数据。
七、总结:Canvas 核心知识体系
知识模块 | 核心内容 |
---|---|
基础绘制 | 直线、多边形、圆形、椭圆、文字,beginPath() /stroke() /fill() |
样式设置 | 线条(lineWidth /strokeStyle )、填充(fillStyle )、阴影、渐变 |
高级功能 | 贝塞尔曲线、2D 变换(平移 / 旋转 / 缩放)、图形组合、图片操作 |
交互与动画 | 鼠标事件、requestAnimationFrame 、定时重绘(如钟表) |
性能优化 | 局部清除、离屏 Canvas、减少样式修改 |
Canvas 是前端可视化的核心技术,可应用于数据图表(如 ECharts 底层)、小游戏、图片处理、自定义组件等场景,掌握其基础绘制与高级功能,能极大扩展前端开发的边界。