【大前端系列02】HTML5 Canvas绘图技术全解析:从入门到精通

发布于:2025-03-29 ⋅ 阅读:(25) ⋅ 点赞:(0)

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();

📌 性能优化

性能优化技巧

  1. 多层Canvas:分离静态和动态内容

    // 静态内容 Canvas
    const bgCanvas = document.createElement('canvas');
    const bgCtx = bgCanvas.getContext('2d');
    
    // 动态内容 Canvas
    const fgCanvas = document.createElement('canvas');
    const fgCtx = fgCanvas.getContext('2d');
    
  2. 状态优化:减少状态变更,按状态分组绘制

    // 优化后:按颜色分组绘制
    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);
    }
    
  3. 离屏渲染:预渲染复杂图形

    // 创建离屏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在可视化、游戏和交互体验领域的应用将更加广泛。


作者: 秦若宸 - 全栈工程师,擅长前端技术与架构设计,个人简历