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...

相关推荐
我是谁谁2 天前
Canvas 高级应用与实战项目<3>
javascript·css·canvas
我是谁谁2 天前
Canvas 应用示例<1>
canvas
LuYu3 天前
vue 版本 canvas 绘图工具来啦!像写 React Native(RN)一样实现canvas 绘图任务
前端·canvas
枫荷3 天前
解决canvas绘图模糊问题
前端·canvas
yaoganjili3 天前
WebGL打开 3D 世界的大门(五):正射投影
前端·canvas·动效
亦黑迷失4 天前
canvas + ts 实现将图片一分为二的功能,并打包发布至 npm
前端·typescript·canvas
张风捷特烈8 天前
Flutter 伪3D绘制#03 | 轴测投影原理分析
android·flutter·canvas
飘尘17 天前
春天来了,来生成一棵独属自己的花树吧!
前端·javascript·canvas
唐某人丶18 天前
从零基于 Canvas 实现一个文本渲染器
前端·canvas