前端技术探索系列:HTML5 Canvas 绘图详解 🎨
开篇寄语 👋
前端开发者们,
在前四篇文章中,我们探讨了 HTML5 的各种特性。今天,让我们一起探索 Canvas 的神奇世界,看看如何用代码创造出绚丽的视觉效果。
一、Canvas 基础入门 🖌️
1. 创建 Canvas 环境
<canvas id="myCanvas" width="800" height="600">
您的浏览器不支持 Canvas 元素
</canvas>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 设置画布尺寸为设备像素比
function setCanvasSize() {
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
ctx.scale(dpr, dpr);
}
2. 基础图形绘制 ⭐
class CanvasDrawing {
constructor(ctx) {
this.ctx = ctx;
}
// 矩形
drawRect(x, y, width, height, style = '#000') {
this.ctx.fillStyle = style;
this.ctx.fillRect(x, y, width, height);
}
// 圆形
drawCircle(x, y, radius, style = '#000') {
this.ctx.beginPath();
this.ctx.arc(x, y, radius, 0, Math.PI * 2);
this.ctx.fillStyle = style;
this.ctx.fill();
}
// 线条
drawLine(startX, startY, endX, endY, style = '#000', width = 1) {
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(endX, endY);
this.ctx.strokeStyle = style;
this.ctx.lineWidth = width;
this.ctx.stroke();
}
// 文本
drawText(text, x, y, style = '#000', font = '16px Arial') {
this.ctx.font = font;
this.ctx.fillStyle = style;
this.ctx.fillText(text, x, y);
}
}
3. 路径与形状 ✨
class PathDrawing {
constructor(ctx) {
this.ctx = ctx;
}
// 绘制多边形
drawPolygon(points, style = '#000', fill = true) {
this.ctx.beginPath();
this.ctx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
this.ctx.lineTo(points[i].x, points[i].y);
}
this.ctx.closePath();
if (fill) {
this.ctx.fillStyle = style;
this.ctx.fill();
} else {
this.ctx.strokeStyle = style;
this.ctx.stroke();
}
}
// 贝塞尔曲线
drawCurve(startX, startY, cp1x, cp1y, cp2x, cp2y, endX, endY) {
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
this.ctx.stroke();
}
}
二、高级绘图技术 🎯
1. 变换操作
class CanvasTransform {
constructor(ctx) {
this.ctx = ctx;
}
// 保存当前状态
save() {
this.ctx.save();
}
// 恢复状态
restore() {
this.ctx.restore();
}
// 旋转
rotate(angle) {
this.ctx.rotate(angle);
}
// 缩放
scale(x, y) {
this.ctx.scale(x, y);
}
// 平移
translate(x, y) {
this.ctx.translate(x, y);
}
// 矩阵变换
transform(a, b, c, d, e, f) {
this.ctx.transform(a, b, c, d, e, f);
}
}
2. 图像合成与滤镜 🖼️
class CanvasEffects {
constructor(ctx) {
this.ctx = ctx;
}
// 设置混合模式
setComposite(type = 'source-over') {
this.ctx.globalCompositeOperation = type;
}
// 设置透明度
setAlpha(alpha) {
this.ctx.globalAlpha = alpha;
}
// 应用阴影
setShadow(color, blur, offsetX, offsetY) {
this.ctx.shadowColor = color;
this.ctx.shadowBlur = blur;
this.ctx.shadowOffsetX = offsetX;
this.ctx.shadowOffsetY = offsetY;
}
// 创建渐变
createGradient(x1, y1, x2, y2, colorStops) {
const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
colorStops.forEach(([offset, color]) => {
gradient.addColorStop(offset, color);
});
return gradient;
}
}
3. 像素操作 📊
class PixelManipulation {
constructor(ctx) {
this.ctx = ctx;
}
// 获取像素数据
getPixelData(x, y, width, height) {
return this.ctx.getImageData(x, y, width, height);
}
// 设置像素数据
setPixelData(imageData, x, y) {
this.ctx.putImageData(imageData, x, y);
}
// 像素处理(如:灰度化)
toGrayscale(imageData) {
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] = avg; // R
data[i + 1] = avg; // G
data[i + 2] = avg; // B
}
return imageData;
}
}
三、动画与交互 🎬
1. 动画循环管理
class AnimationManager {
constructor() {
this.animations = new Set();
this.isRunning = false;
this.lastTime = 0;
}
// 添加动画
addAnimation(callback) {
this.animations.add(callback);
if (!this.isRunning) {
this.start();
}
}
// 移除动画
removeAnimation(callback) {
this.animations.delete(callback);
if (this.animations.size === 0) {
this.stop();
}
}
// 开始动画循环
start() {
this.isRunning = true;
this.lastTime = performance.now();
this.animate();
}
// 停止动画循环
stop() {
this.isRunning = false;
}
// 动画帧
animate = (currentTime) => {
if (!this.isRunning) return;
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
for (const animation of this.animations) {
animation(deltaTime);
}
requestAnimationFrame(this.animate);
}
}
2. 交互事件处理 🖱️
class CanvasInteraction {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.events = new Map();
this.setupEvents();
}
setupEvents() {
this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
this.canvas.addEventListener('click', this.handleClick.bind(this));
}
// 获取画布相对坐标
getCanvasPoint(event) {
const rect = this.canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
// 添加事件监听
on(eventName, callback) {
if (!this.events.has(eventName)) {
this.events.set(eventName, new Set());
}
this.events.get(eventName).add(callback);
}
// 触发事件
trigger(eventName, data) {
if (this.events.has(eventName)) {
for (const callback of this.events.get(eventName)) {
callback(data);
}
}
}
handleMouseDown(event) {
const point = this.getCanvasPoint(event);
this.trigger('mousedown', point);
}
handleMouseMove(event) {
const point = this.getCanvasPoint(event);
this.trigger('mousemove', point);
}
handleMouseUp(event) {
const point = this.getCanvasPoint(event);
this.trigger('mouseup', point);
}
handleClick(event) {
const point = this.getCanvasPoint(event);
this.trigger('click', point);
}
}
四、实践项目:交互式图表 📊
class InteractiveChart {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.interaction = new CanvasInteraction(canvas);
this.animation = new AnimationManager();
this.data = [];
this.options = {
padding: 40,
animationDuration: 1000,
barColor: '#4CAF50',
hoverColor: '#2196F3',
textColor: '#333',
gridColor: '#ddd'
};
this.setupInteractions();
}
// 设置数据
setData(data) {
this.data = data;
this.calculateScales();
this.draw();
}
// 计算比例尺
calculateScales() {
const values = this.data.map(d => d.value);
this.maxValue = Math.max(...values);
this.minValue = Math.min(...values);
this.barWidth = (this.canvas.width - this.options.padding * 2) / this.data.length;
this.scaleY = (this.canvas.height - this.options.padding * 2) / (this.maxValue - this.minValue);
}
// 绘制网格
drawGrid() {
const { ctx, canvas, options } = this;
ctx.strokeStyle = options.gridColor;
ctx.lineWidth = 0.5;
// 横向网格线
for (let i = 0; i <= 5; i++) {
const y = options.padding + (canvas.height - options.padding * 2) * (i / 5);
ctx.beginPath();
ctx.moveTo(options.padding, y);
ctx.lineTo(canvas.width - options.padding, y);
ctx.stroke();
}
}
// 绘制柱状图
drawBars() {
const { ctx, options } = this;
this.data.forEach((item, index) => {
const x = options.padding + index * this.barWidth;
const height = item.value * this.scaleY;
const y = this.canvas.height - options.padding - height;
ctx.fillStyle = item.hover ? options.hoverColor : options.barColor;
ctx.fillRect(x, y, this.barWidth - 2, height);
// 绘制标签
ctx.fillStyle = options.textColor;
ctx.textAlign = 'center';
ctx.fillText(item.label, x + this.barWidth / 2, this.canvas.height - options.padding + 20);
});
}
// 清空画布
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
// 绘制图表
draw() {
this.clear();
this.drawGrid();
this.drawBars();
}
// 设置交互
setupInteractions() {
this.interaction.on('mousemove', point => {
const index = Math.floor((point.x - this.options.padding) / this.barWidth);
this.data.forEach((item, i) => {
item.hover = i === index;
});
this.draw();
});
this.interaction.on('click', point => {
const index = Math.floor((point.x - this.options.padding) / this.barWidth);
if (index >= 0 && index < this.data.length) {
console.log('Clicked bar:', this.data[index]);
}
});
}
}
// 使用示例
const chart = new InteractiveChart(document.getElementById('chartCanvas'));
chart.setData([
{ label: '一月', value: 30 },
{ label: '二月', value: 50 },
{ label: '三月', value: 20 },
{ label: '四月', value: 40 },
{ label: '五月', value: 60 }
]);
性能优化建议 🚀
画布优化
- 使用
requestAnimationFrame
- 避免频繁的状态切换
- 使用离屏 Canvas 缓存
- 使用
绘图优化
- 批量绘制
- 避免不必要的路径操作
- 使用适当的图形原语
事件处理
- 使用事件委托
- 节流和防抖
- 优化碰撞检测
浏览器兼容性 🌐
特性 | Chrome | Firefox | Safari | Edge |
---|---|---|---|---|
Canvas 2D | ✅ | ✅ | ✅ | ✅ |
Path2D | ✅ | ✅ | ✅ | ✅ |
ImageData | ✅ | ✅ | ✅ | ✅ |
实用工具推荐 🛠️
图形库
- Fabric.js
- Paper.js
- Two.js
图表库
- Chart.js
- D3.js
- ECharts
调试工具
- Canvas Debugger
- FPS Monitor
- Performance Panel
总结 🎯
Canvas 为我们提供了强大的绘图能力:
- 基础图形绘制 ✏️
- 高级变换效果 🎨
- 动画与交互 🎬
- 像素级操作 🖼️
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻