Canvas 进阶应用示例<2>

Canvas 进阶应用示例

下面我将介绍更多 Canvas 的进阶用法,包括变换、合成、像素操作以及更复杂的动画效果。

1. 坐标变换

Canvas 提供了几种变换图形位置和方向的方法:

javascript 复制代码
// 保存当前状态(包括变换矩阵)
ctx.save();

// 平移
ctx.translate(100, 100);
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 50, 50);

// 旋转
ctx.rotate(Math.PI/4); // 45度
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 50, 50);

// 缩放
ctx.scale(1.5, 1.5);
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 50, 50);

// 恢复之前保存的状态
ctx.restore();

2. 路径与复杂图形

javascript 复制代码
// 绘制复杂路径
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.quadraticCurveTo(200, 100, 150, 150); // 二次贝塞尔曲线
ctx.lineTo(50, 150);
ctx.bezierCurveTo(0, 100, 0, 100, 50, 50); // 三次贝塞尔曲线
ctx.closePath();

ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.fill();
ctx.strokeStyle = 'black';
ctx.stroke();

3. 图像合成

javascript 复制代码
// 绘制第一个图形
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 100, 100);

// 设置合成模式
ctx.globalCompositeOperation = 'lighter'; // 尝试不同的模式:source-over, multiply, screen等

// 绘制第二个图形(会与第一个图形按指定模式合成)
ctx.fillStyle = 'blue';
ctx.fillRect(100, 100, 100, 100);

// 重置合成模式
ctx.globalCompositeOperation = 'source-over';

4. 像素操作

javascript 复制代码
// 绘制原始图像
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 200, 100);

// 获取像素数据
const imageData = ctx.getImageData(50, 50, 200, 100);
const data = imageData.data;

// 修改像素数据(反色效果)
for (let 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
    // data[i+3] 是alpha通道,保持不变
}

// 将修改后的像素数据放回canvas
ctx.putImageData(imageData, 50, 50);

5. 高级动画 - 粒子系统

javascript 复制代码
// 创建粒子数组
const particles = [];
for (let i = 0; i < 100; i++) {
    particles.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        radius: Math.random() * 5 + 1,
        color: `rgba(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, 0.7)`,
        speedX: Math.random() * 2 - 1,
        speedY: Math.random() * 2 - 1
    });
}

function animateParticles() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 更新并绘制所有粒子
    particles.forEach(p => {
        // 更新位置
        p.x += p.speedX;
        p.y += p.speedY;
        
        // 边界检测
        if (p.x < 0 || p.x > canvas.width) p.speedX = -p.speedX;
        if (p.y < 0 || p.y > canvas.height) p.speedY = -p.speedY;
        
        // 绘制粒子
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
        ctx.fillStyle = p.color;
        ctx.fill();
    });
    
    // 绘制粒子间的连线
    drawConnections();
    
    requestAnimationFrame(animateParticles);
}

function drawConnections() {
    for (let i = 0; i < particles.length; i++) {
        for (let j = i + 1; j < particles.length; j++) {
            const p1 = particles[i];
            const p2 = particles[j];
            const dx = p1.x - p2.x;
            const dy = p1.y - p2.y;
            const distance = Math.sqrt(dx * dx + dy * dy);
            
            if (distance < 100) {
                ctx.beginPath();
                ctx.moveTo(p1.x, p1.y);
                ctx.lineTo(p2.x, p2.y);
                ctx.strokeStyle = `rgba(150, 150, 150, ${1 - distance/100})`;
                ctx.lineWidth = 0.5;
                ctx.stroke();
            }
        }
    }
}

animateParticles();

6. 绘制图表

javascript 复制代码
// 示例数据
const data = [30, 45, 60, 35, 70, 40, 80];
const labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];

function drawBarChart() {
    const chartWidth = 400;
    const chartHeight = 200;
    const chartX = 50;
    const chartY = 50;
    const barWidth = 40;
    const gap = 20;
    const maxValue = Math.max(...data);
    
    // 绘制坐标轴
    ctx.beginPath();
    ctx.moveTo(chartX, chartY);
    ctx.lineTo(chartX, chartY + chartHeight);
    ctx.lineTo(chartX + chartWidth, chartY + chartHeight);
    ctx.strokeStyle = 'black';
    ctx.stroke();
    
    // 绘制柱状图
    data.forEach((value, index) => {
        const barHeight = (value / maxValue) * chartHeight;
        const x = chartX + index * (barWidth + gap) + gap;
        const y = chartY + chartHeight - barHeight;
        
        ctx.fillStyle = `hsl(${index * 50}, 70%, 60%)`;
        ctx.fillRect(x, y, barWidth, barHeight);
        
        // 绘制标签
        ctx.fillStyle = 'black';
        ctx.font = '12px Arial';
        ctx.fillText(labels[index], x + barWidth/2 - 10, chartY + chartHeight + 15);
        ctx.fillText(value.toString(), x + barWidth/2 - 5, y - 5);
    });
}

drawBarChart();

7. 图像滤镜效果

javascript 复制代码
// 加载图像
const img = new Image();
img.src = 'path/to/image.jpg';
img.onload = function() {
    // 绘制原始图像
    ctx.drawImage(img, 50, 50, 200, 200);
    
    // 应用灰度滤镜
    applyFilter(50, 300, 'grayscale');
    
    // 应用复古滤镜
    applyFilter(300, 50, 'sepia');
    
    // 应用反色滤镜
    applyFilter(300, 300, 'invert');
};

function applyFilter(x, y, type) {
    // 绘制图像到临时位置
    ctx.drawImage(img, -1000, -1000, 200, 200);
    
    // 获取像素数据
    const imageData = ctx.getImageData(-1000, -1000, 200, 200);
    const data = imageData.data;
    
    // 应用滤镜
    for (let i = 0; i < data.length; i += 4) {
        const r = data[i];
        const g = data[i+1];
        const b = data[i+2];
        
        switch(type) {
            case 'grayscale':
                const avg = 0.299 * r + 0.587 * g + 0.114 * b;
                data[i] = data[i+1] = data[i+2] = avg;
                break;
            case 'sepia':
                data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
                data[i+1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
                data[i+2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
                break;
            case 'invert':
                data[i] = 255 - r;
                data[i+1] = 255 - g;
                data[i+2] = 255 - b;
                break;
        }
    }
    
    // 将处理后的图像放回指定位置
    ctx.putImageData(imageData, x, y);
}

8. 性能优化技巧

  1. 离屏渲染:对于复杂的、重复绘制的图形,可以先在离屏 canvas 上绘制,然后复制到主 canvas。
javascript 复制代码
// 创建离屏canvas
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = 100;
offscreenCanvas.height = 100;
const offscreenCtx = offscreenCanvas.getContext('2d');

// 在离屏canvas上绘制复杂图形
offscreenCtx.fillStyle = 'red';
offscreenCtx.beginPath();
offscreenCtx.arc(50, 50, 40, 0, Math.PI * 2);
offscreenCtx.fill();

// 复制到主canvas(可以多次复制)
ctx.drawImage(offscreenCanvas, 50, 50);
ctx.drawImage(offscreenCanvas, 200, 100);
  1. 避免频繁的绘制操作:只在必要时重绘整个 canvas。

  2. 使用 requestAnimationFrame 而不是 setInterval 或 setTimeout 来实现动画。

  3. 减少绘制区域 :使用 clearRect() 只清除需要更新的区域,而不是整个 canvas。

这些进阶技术可以帮助你创建更复杂、更高效的 Canvas 应用。根据项目需求,你可以组合使用这些技术来实现各种效果。

上一个文字 juejin.cn/post/749159...

相关推荐
wayhome在哪12 小时前
用 fabric.js 搞定电子签名拖拽合成图片
javascript·产品·canvas
德育处主任13 小时前
p5.js 掌握圆锥体 cone
前端·数据可视化·canvas
德育处主任1 天前
p5.js 3D 形状 "预制工厂"——buildGeometry ()
前端·javascript·canvas
德育处主任3 天前
p5.js 3D盒子的基础用法
前端·数据可视化·canvas
掘金安东尼4 天前
2分钟创建一个“不依赖任何外部库”的粒子动画背景
前端·面试·canvas
百万蹄蹄向前冲4 天前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae
用户2519162427116 天前
Canvas之画图板
前端·javascript·canvas
FogLetter9 天前
玩转Canvas:从静态图像到动态动画的奇妙之旅
前端·canvas
用户25191624271110 天前
Canvas之贪吃蛇
前端·javascript·canvas
用户25191624271111 天前
Canvas之粒子烟花
前端·javascript·canvas