HTML5 Canvas绘图技术全解析
系列: 「全栈进化:大前端开发完全指南」系列第2篇(共5篇)
核心: Canvas绘图技术的基本原理与高级应用技巧
📌 引言
Canvas是HTML5引入的绘图API,提供可编程的矩形绘图区域,使用JavaScript动态生成图形、图表、动画效果和图像处理。Canvas的本质是一个位图画布,每个像素都可以被精确控制。
为什么学习Canvas?
- 创建复杂视觉效果和交互体验
- 高性能图形渲染(优于DOM操作)
- Web游戏开发的基础技术
- 数据可视化的理想工具
- 强大的图像处理能力
📌 基础概念
Canvas元素与上下文
<!-- 创建Canvas元素 -->
<canvas id="myCanvas" width="600" height="400">
您的浏览器不支持Canvas,请升级浏览器
</canvas>
// 获取绘图上下文
const canvas = document.getElementById('myCanvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
// 后续使用ctx进行绘图操作
}
Canvas特点:
- 默认尺寸:300px × 150px
- 使用width和height属性设置实际绘图尺寸
- 坐标系:左上角为原点(0,0),X轴向右,Y轴向下
核心API分类
API类别 | 主要功能 | 核心方法 |
---|---|---|
路径绘制 | 创建图形路径 | beginPath(), moveTo(), lineTo() |
图形绘制 | 基本几何图形 | rect(), arc(), ellipse() |
样式设置 | 颜色和线条样式 | fillStyle, strokeStyle, lineWidth |
文本处理 | 绘制文本 | fillText(), font, textAlign |
图像操作 | 处理图像 | drawImage(), getImageData() |
变换操作 | 变换绘图 | translate(), rotate(), scale() |
📌 基础绘图技术
绘制基本图形
矩形绘制:
// 填充矩形
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 100, 80);
// 描边矩形
ctx.strokeStyle = 'red';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 80);
// 清除区域
ctx.clearRect(30, 30, 60, 40);
路径图形:
// 绘制三角形
ctx.beginPath();
ctx.moveTo(200, 50); // 起点
ctx.lineTo(250, 150); // 第二个点
ctx.lineTo(150, 150); // 第三个点
ctx.closePath(); // 闭合路径
ctx.fillStyle = 'green';
ctx.fill(); // 填充
ctx.stroke(); // 描边
圆形与弧线:
// 绘制圆形
ctx.beginPath();
ctx.arc(350, 80, 40, 0, Math.PI * 2); // x, y, 半径, 起始角度, 结束角度
ctx.fillStyle = 'orange';
ctx.fill();
// 绘制半圆
ctx.beginPath();
ctx.arc(450, 80, 40, 0, Math.PI);
ctx.stroke();
样式设置
颜色与透明度:
ctx.fillStyle = 'red'; // 命名颜色
ctx.fillStyle = '#00ff00'; // 十六进制
ctx.fillStyle = 'rgb(0, 0, 255)'; // RGB
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // RGBA带透明度
ctx.globalAlpha = 0.5; // 全局透明度
线条样式:
ctx.lineWidth = 5; // 线宽
ctx.lineCap = 'round'; // 线条端点:butt, round, square
ctx.lineJoin = 'bevel'; // 线条连接:miter, round, bevel
ctx.setLineDash([5, 10]); // 虚线模式:实线5px,间隔10px
渐变与图案:
// 线性渐变
const gradient = ctx.createLinearGradient(50, 300, 150, 400);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(1, 'red');
ctx.fillStyle = gradient;
ctx.fillRect(50, 300, 100, 100);
// 径向渐变
const radial = ctx.createRadialGradient(275, 350, 10, 275, 350, 50);
radial.addColorStop(0, 'white');
radial.addColorStop(1, 'green');
ctx.fillStyle = radial;
ctx.fillRect(225, 300, 100, 100);
文本绘制
// 设置文本样式
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'center'; // start, end, left, right, center
ctx.textBaseline = 'middle'; // top, middle, bottom, alphabetic
// 填充文本
ctx.fillStyle = 'black';
ctx.fillText('Hello Canvas!', 250, 480);
// 描边文本
ctx.strokeStyle = 'blue';
ctx.strokeText('Stroke Text', 250, 520);
📌 高级绘图技术
图像处理
// 加载并绘制图像
const img = new Image();
img.src = 'example.jpg';
img.onload = function() {
// 基本绘制
ctx.drawImage(img, 50, 50);
// 缩放绘制
ctx.drawImage(img, 200, 50, 100, 75);
// 裁剪绘制
ctx.drawImage(img, 0, 0, 100, 100, 350, 50, 100, 100);
};
// 像素操作(灰度处理)
const imageData = ctx.getImageData(0, 0, 100, 100);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i+1] + data[i+2]) / 3;
data[i] = data[i+1] = data[i+2] = avg;
}
ctx.putImageData(imageData, 0, 0);
变换与动画
基本变换:
// 保存当前状态
ctx.save();
// 移动原点
ctx.translate(50, 50);
// 旋转坐标系(弧度)
ctx.rotate(Math.PI / 4); // 45度
// 缩放
ctx.scale(1.5, 0.5);
// 绘制内容
ctx.fillRect(0, 0, 50, 50);
// 恢复状态
ctx.restore();
创建动画:
function animate() {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制图形
ctx.fillRect(x, y, 50, 50);
// 更新位置
x += 2;
if (x > canvas.width) x = -50;
// 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画
animate();
高级效果
剪切区域:
// 创建剪切区域
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.clip();
// 在剪切区域内绘制
ctx.fillRect(50, 50, 100, 100); // 只显示圆形内部
阴影效果:
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.fillText('Shadow Effect', 100, 100);
合成模式:
// 先绘制红色圆形
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(100, 100, 40, 0, Math.PI * 2);
ctx.fill();
// 设置合成模式
ctx.globalCompositeOperation = 'multiply';
// 再绘制蓝色圆形
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(140, 100, 40, 0, Math.PI * 2);
ctx.fill();
📌 实战案例
数据可视化:柱状图
function drawBarChart(data, labels) {
const barWidth = 40;
const spacing = 20;
const maxValue = Math.max(...data);
// 绘制坐标轴
ctx.beginPath();
ctx.moveTo(50, 400);
ctx.lineTo(50, 50);
ctx.moveTo(50, 400);
ctx.lineTo(600, 400);
ctx.stroke();
// 绘制柱形
for (let i = 0; i < data.length; i++) {
const height = (data[i] / maxValue) * 300;
const x = 80 + i * (barWidth + spacing);
const y = 400 - height;
// 渐变填充
const gradient = ctx.createLinearGradient(x, y, x, 400);
gradient.addColorStop(0, 'rgba(0, 123, 255, 0.8)');
gradient.addColorStop(1, 'rgba(0, 123, 255, 0.2)');
ctx.fillStyle = gradient;
ctx.fillRect(x, y, barWidth, height);
ctx.strokeRect(x, y, barWidth, height);
// 绘制数值和标签
ctx.fillStyle = 'black';
ctx.fillText(data[i], x + barWidth/2, y - 10);
ctx.fillText(labels[i], x + barWidth/2, 420);
}
}
const data = [65, 59, 80, 81, 56, 55, 40];
const labels = ['一', '二', '三', '四', '五', '六', '日'];
drawBarChart(data, labels);
交互效果:拖动
// 创建可拖动元素
const circle = {
x: 100, y: 100, radius: 30,
color: 'red', isDragging: false
};
function drawCircle() {
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
ctx.fillStyle = circle.color;
ctx.fill();
}
// 检测鼠标位置
function isInCircle(x, y) {
const dx = x - circle.x;
const dy = y - circle.y;
return dx*dx + dy*dy <= circle.radius * circle.radius;
}
// 添加事件监听
canvas.addEventListener('mousedown', function(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (isInCircle(x, y)) {
circle.isDragging = true;
}
});
canvas.addEventListener('mousemove', function(e) {
if (circle.isDragging) {
const rect = canvas.getBoundingClientRect();
circle.x = e.clientX - rect.left;
circle.y = e.clientY - rect.top;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawCircle();
}
});
canvas.addEventListener('mouseup', function() {
circle.isDragging = false;
});
粒子动画效果
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 5 + 1;
this.speedX = Math.random() * 3 - 1.5;
this.speedY = Math.random() * 3 - 1.5;
this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (this.size > 0.2) this.size -= 0.1;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
const particles = [];
function animate() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 添加新粒子
particles.push(new Particle(canvas.width/2, canvas.height/2));
// 更新和绘制粒子
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
if (particles[i].size <= 0.2) {
particles.splice(i, 1);
i--;
}
}
requestAnimationFrame(animate);
}
animate();
📌 性能优化
性能优化技巧
多层Canvas:分离静态和动态内容
// 静态内容 Canvas const bgCanvas = document.createElement('canvas'); const bgCtx = bgCanvas.getContext('2d'); // 动态内容 Canvas const fgCanvas = document.createElement('canvas'); const fgCtx = fgCanvas.getContext('2d');
状态优化:减少状态变更,按状态分组绘制
// 优化后:按颜色分组绘制 ctx.fillStyle = 'blue'; for (let i = 0; i < items.length; i++) { if (!items[i].selected) ctx.fillRect(items[i].x, items[i].y, 10, 10); } ctx.fillStyle = 'red'; for (let i = 0; i < items.length; i++) { if (items[i].selected) ctx.fillRect(items[i].x, items[i].y, 10, 10); }
离屏渲染:预渲染复杂图形
// 创建离屏Canvas预渲染 const offscreenCanvas = document.createElement('canvas'); const offCtx = offscreenCanvas.getContext('2d'); // 预先绘制复杂图形 drawComplexShape(offCtx); // 多次使用预渲染内容 ctx.drawImage(offscreenCanvas, x, y);
常见问题与解决
高DPI屏幕适配:
function setupHiDPICanvas(canvas) {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
canvas.style.width = rect.width + 'px';
canvas.style.height = rect.height + 'px';
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
return ctx;
}
浏览器兼容性:
// 特性检测
function isCanvasSupported() {
const canvas = document.createElement('canvas');
return !!(canvas.getContext && canvas.getContext('2d'));
}
📌 总结
Canvas为Web开发提供了强大的图形绘制能力,应用场景包括:
- 数据可视化和图表
- 游戏开发
- 图像处理和编辑
- 动画效果
- 交互式应用
掌握Canvas后,可以进一步学习:
- WebGL (3D图形渲染)
- Three.js (3D引擎)
- Pixi.js (2D游戏引擎)
- 专业数据可视化库
随着Web技术的发展,Canvas在可视化、游戏和交互体验领域的应用将更加广泛。
作者: 秦若宸 - 全栈工程师,擅长前端技术与架构设计,个人简历