Canvas 高级应用与实战项目<3>

Canvas 高级应用与实战项目

下面我将介绍 Canvas 更高级的应用场景,包括游戏开发、数据可视化、图像处理等实战项目,并提供完整的代码示例。

1. 简单游戏开发 - 打砖块

javascript 复制代码
// 游戏状态
const game = {
    ball: { x: 250, y: 150, radius: 10, dx: 4, dy: -4 },
    paddle: { x: 200, width: 100, height: 15 },
    bricks: [],
    brickRowCount: 5,
    brickColumnCount: 8,
    brickWidth: 60,
    brickHeight: 20,
    brickPadding: 10,
    brickOffsetTop: 60,
    brickOffsetLeft: 30,
    score: 0,
    lives: 3
};

// 初始化砖块
function initBricks() {
    for (let c = 0; c < game.brickColumnCount; c++) {
        game.bricks[c] = [];
        for (let r = 0; r < game.brickRowCount; r++) {
            game.bricks[c][r] = { x: 0, y: 0, status: 1 };
        }
    }
}

// 绘制游戏元素
function draw() {
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 绘制球
    ctx.beginPath();
    ctx.arc(game.ball.x, game.ball.y, game.ball.radius, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
    
    // 绘制挡板
    ctx.beginPath();
    ctx.rect(game.paddle.x, canvas.height-game.paddle.height, 
             game.paddle.width, game.paddle.height);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
    
    // 绘制砖块
    for (let c = 0; c < game.brickColumnCount; c++) {
        for (let r = 0; r < game.brickRowCount; r++) {
            if (game.bricks[c][r].status === 1) {
                const brickX = c*(game.brickWidth+game.brickPadding)+game.brickOffsetLeft;
                const brickY = r*(game.brickHeight+game.brickPadding)+game.brickOffsetTop;
                game.bricks[c][r].x = brickX;
                game.bricks[c][r].y = brickY;
                ctx.beginPath();
                ctx.rect(brickX, brickY, game.brickWidth, game.brickHeight);
                ctx.fillStyle = `hsl(${r*60}, 100%, 50%)`;
                ctx.fill();
                ctx.closePath();
            }
        }
    }
    
    // 绘制分数
    ctx.font = "16px Arial";
    ctx.fillStyle = "#0095DD";
    ctx.fillText("Score: "+game.score, 8, 20);
    
    // 绘制生命值
    ctx.fillText("Lives: "+game.lives, canvas.width-65, 20);
}

// 碰撞检测
function collisionDetection() {
    for (let c = 0; c < game.brickColumnCount; c++) {
        for (let r = 0; r < game.brickRowCount; r++) {
            const b = game.bricks[c][r];
            if (b.status === 1) {
                if (game.ball.x > b.x && game.ball.x < b.x+game.brickWidth &&
                    game.ball.y > b.y && game.ball.y < b.y+game.brickHeight) {
                    game.ball.dy = -game.ball.dy;
                    b.status = 0;
                    game.score++;
                    if (game.score === game.brickRowCount*game.brickColumnCount) {
                        alert("YOU WIN, CONGRATULATIONS!");
                        document.location.reload();
                    }
                }
            }
        }
    }
}

// 游戏逻辑更新
function update() {
    // 球移动
    game.ball.x += game.ball.dx;
    game.ball.y += game.ball.dy;
    
    // 边界碰撞检测
    if (game.ball.x + game.ball.dx > canvas.width-game.ball.radius || 
        game.ball.x + game.ball.dx < game.ball.radius) {
        game.ball.dx = -game.ball.dx;
    }
    
    if (game.ball.y + game.ball.dy < game.ball.radius) {
        game.ball.dy = -game.ball.dy;
    } else if (game.ball.y + game.ball.dy > canvas.height-game.ball.radius) {
        if (game.ball.x > game.paddle.x && game.ball.x < game.paddle.x + game.paddle.width) {
            game.ball.dy = -game.ball.dy;
        } else {
            game.lives--;
            if (!game.lives) {
                alert("GAME OVER");
                document.location.reload();
            } else {
                game.ball.x = 250;
                game.ball.y = 150;
                game.ball.dx = 4;
                game.ball.dy = -4;
                game.paddle.x = 200;
            }
        }
    }
    
    // 挡板控制
    if (rightPressed && game.paddle.x < canvas.width-game.paddle.width) {
        game.paddle.x += 7;
    } else if (leftPressed && game.paddle.x > 0) {
        game.paddle.x -= 7;
    }
    
    collisionDetection();
}

// 键盘控制
let rightPressed = false;
let leftPressed = false;

document.addEventListener("keydown", keyDownHandler);
document.addEventListener("keyup", keyUpHandler);

function keyDownHandler(e) {
    if (e.key === "Right" || e.key === "ArrowRight") {
        rightPressed = true;
    } else if (e.key === "Left" || e.key === "ArrowLeft") {
        leftPressed = true;
    }
}

function keyUpHandler(e) {
    if (e.key === "Right" || e.key === "ArrowRight") {
        rightPressed = false;
    } else if (e.key === "Left" || e.key === "ArrowLeft") {
        leftPressed = false;
    }
}

// 鼠标控制
canvas.addEventListener("mousemove", mouseMoveHandler);

function mouseMoveHandler(e) {
    const relativeX = e.clientX - canvas.offsetLeft;
    if (relativeX > 0 && relativeX < canvas.width) {
        game.paddle.x = relativeX - game.paddle.width/2;
    }
}

// 游戏循环
function gameLoop() {
    update();
    draw();
    requestAnimationFrame(gameLoop);
}

initBricks();
gameLoop();

2. 数据可视化 - 动态折线图

javascript 复制代码
// 模拟实时数据
const data = {
    labels: Array(60).fill(0).map((_, i) => i),
    values: Array(60).fill(0),
    maxPoints: 60,
    currentIndex: 0
};

// 更新数据
function updateData() {
    // 模拟数据变化
    const newValue = Math.random() * 100 + 50;
    data.values[data.currentIndex] = newValue;
    data.currentIndex = (data.currentIndex + 1) % data.maxPoints;
}

// 绘制折线图
function drawLineChart() {
    const padding = 40;
    const chartWidth = canvas.width - 2*padding;
    const chartHeight = canvas.height - 2*padding;
    
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 绘制坐标轴
    ctx.beginPath();
    ctx.moveTo(padding, padding);
    ctx.lineTo(padding, canvas.height - padding);
    ctx.lineTo(canvas.width - padding, canvas.height - padding);
    ctx.strokeStyle = '#666';
    ctx.stroke();
    
    // 绘制网格线
    ctx.strokeStyle = '#eee';
    for (let i = 0; i <= 10; i++) {
        // 水平网格线
        ctx.beginPath();
        const y = padding + (chartHeight - i*chartHeight/10);
        ctx.moveTo(padding, y);
        ctx.lineTo(canvas.width - padding, y);
        ctx.stroke();
        
        // Y轴标签
        ctx.fillStyle = '#666';
        ctx.font = '10px Arial';
        ctx.fillText(Math.round(i*150/10), 5, y + 5);
    }
    
    // 绘制折线
    ctx.beginPath();
    ctx.strokeStyle = '#3498db';
    ctx.lineWidth = 2;
    
    const visibleData = [];
    for (let i = 0; i < data.maxPoints; i++) {
        const index = (data.currentIndex + i) % data.maxPoints;
        visibleData.push(data.values[index]);
    }
    
    const maxValue = Math.max(...visibleData);
    const minValue = Math.min(...visibleData);
    const valueRange = maxValue - minValue;
    
    visibleData.forEach((value, i) => {
        const x = padding + (i * chartWidth / (data.maxPoints - 1));
        const y = canvas.height - padding - ((value - minValue) / valueRange) * chartHeight;
        
        if (i === 0) {
            ctx.moveTo(x, y);
        } else {
            ctx.lineTo(x, y);
        }
        
        // 绘制数据点
        ctx.beginPath();
        ctx.arc(x, y, 2, 0, Math.PI*2);
        ctx.fillStyle = '#3498db';
        ctx.fill();
    });
    
    ctx.stroke();
    
    // 绘制标题
    ctx.fillStyle = '#333';
    ctx.font = '16px Arial';
    ctx.fillText('实时数据折线图', canvas.width/2 - 60, 30);
    
    // 绘制当前值
    const currentValue = data.values[(data.currentIndex - 1 + data.maxPoints) % data.maxPoints];
    ctx.fillStyle = '#e74c3c';
    ctx.font = '14px Arial';
    ctx.fillText(`当前值: ${currentValue.toFixed(2)}`, canvas.width - 150, 30);
}

// 动画循环
function animate() {
    updateData();
    drawLineChart();
    requestAnimationFrame(animate);
}

animate();

3. 图像处理 - 图片编辑器

html 复制代码
<div>
    <input type="file" id="imageUpload" accept="image/*">
    <button id="grayscale">灰度化</button>
    <button id="brighten">亮度+</button>
    <button id="darken">亮度-</button>
    <button id="reset">重置</button>
</div>
<canvas id="imageCanvas" width="600" height="400"></canvas>
javascript 复制代码
const imageCanvas = document.getElementById('imageCanvas');
const ctx = imageCanvas.getContext('2d');
const imageUpload = document.getElementById('imageUpload');
let originalImageData = null;

// 上传图片
imageUpload.addEventListener('change', function(e) {
    const file = e.target.files[0];
    if (!file) return;
    
    const reader = new FileReader();
    reader.onload = function(event) {
        const img = new Image();
        img.onload = function() {
            // 调整canvas大小以适应图片
            const ratio = Math.min(
                imageCanvas.width / img.width,
                imageCanvas.height / img.height
            );
            const newWidth = img.width * ratio;
            const newHeight = img.height * ratio;
            
            // 绘制图片
            ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
            ctx.drawImage(img, 
                          (imageCanvas.width - newWidth)/2, 
                          (imageCanvas.height - newHeight)/2, 
                          newWidth, newHeight);
            
            // 保存原始图像数据
            originalImageData = ctx.getImageData(0, 0, imageCanvas.width, imageCanvas.height);
        };
        img.src = event.target.result;
    };
    reader.readAsDataURL(file);
});

// 应用滤镜
function applyFilter(filterFn) {
    if (!originalImageData) return;
    
    const imageData = ctx.getImageData(0, 0, imageCanvas.width, imageCanvas.height);
    const data = imageData.data;
    
    for (let i = 0; i < data.length; i += 4) {
        filterFn(data, i);
    }
    
    ctx.putImageData(imageData, 0, 0);
}

// 滤镜函数
const filters = {
    grayscale: (data, i) => {
        const avg = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
        data[i] = data[i+1] = data[i+2] = avg;
    },
    brighten: (data, i) => {
        data[i] = Math.min(255, data[i] * 1.2);
        data[i+1] = Math.min(255, data[i+1] * 1.2);
        data[i+2] = Math.min(255, data[i+2] * 1.2);
    },
    darken: (data, i) => {
        data[i] = Math.max(0, data[i] * 0.8);
        data[i+1] = Math.max(0, data[i+1] * 0.8);
        data[i+2] = Math.max(0, data[i+2] * 0.8);
    }
};

// 按钮事件
document.getElementById('grayscale').addEventListener('click', () => {
    applyFilter(filters.grayscale);
});

document.getElementById('brighten').addEventListener('click', () => {
    applyFilter(filters.brighten);
});

document.getElementById('darken').addEventListener('click', () => {
    applyFilter(filters.darken);
});

document.getElementById('reset').addEventListener('click', () => {
    if (originalImageData) {
        ctx.putImageData(originalImageData, 0, 0);
    }
});

4. 物理引擎 - 弹跳球模拟

javascript 复制代码
// 物理参数
const physics = {
    gravity: 0.2,
    friction: 0.99,
    bounce: 0.8
};

// 球体
const balls = [];
for (let i = 0; i < 20; i++) {
    balls.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height/2,
        radius: Math.random() * 20 + 10,
        color: `hsl(${Math.random() * 360}, 100%, 50%)`,
        dx: Math.random() * 6 - 3,
        dy: Math.random() * 6 - 3
    });
}

// 绘制所有球
function drawBalls() {
    balls.forEach(ball => {
        ctx.beginPath();
        ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
        ctx.fillStyle = ball.color;
        ctx.fill();
        ctx.closePath();
    });
}

// 更新物理状态
function updatePhysics() {
    balls.forEach(ball => {
        // 应用重力
        ball.dy += physics.gravity;
        
        // 应用摩擦力
        ball.dx *= physics.friction;
        ball.dy *= physics.friction;
        
        // 更新位置
        ball.x += ball.dx;
        ball.y += ball.dy;
        
        // 边界碰撞检测
        if (ball.x + ball.radius > canvas.width) {
            ball.x = canvas.width - ball.radius;
            ball.dx = -ball.dx * physics.bounce;
        } else if (ball.x - ball.radius < 0) {
            ball.x = ball.radius;
            ball.dx = -ball.dx * physics.bounce;
        }
        
        if (ball.y + ball.radius > canvas.height) {
            ball.y = canvas.height - ball.radius;
            ball.dy = -ball.dy * physics.bounce;
        } else if (ball.y - ball.radius < 0) {
            ball.y = ball.radius;
            ball.dy = -ball.dy * physics.bounce;
        }
    });
}

// 球体间碰撞检测
function checkCollisions() {
    for (let i = 0; i < balls.length; i++) {
        for (let j = i + 1; j < balls.length; j++) {
            const ball1 = balls[i];
            const ball2 = balls[j];
            
            const dx = ball2.x - ball1.x;
            const dy = ball2.y - ball1.y;
            const distance = Math.sqrt(dx * dx + dy * dy);
            
            if (distance < ball1.radius + ball2.radius) {
                // 碰撞响应
                const angle = Math.atan2(dy, dx);
                const sin = Math.sin(angle);
                const cos = Math.cos(angle);
                
                // 旋转坐标系
                const vx1 = ball1.dx * cos + ball1.dy * sin;
                const vy1 = ball1.dy * cos - ball1.dx * sin;
                const vx2 = ball2.dx * cos + ball2.dy * sin;
                const vy2 = ball2.dy * cos - ball2.dx * sin;
                
                // 碰撞后速度
                const v1Final = (vx1 * (ball1.radius - ball2.radius) + 2 * ball2.radius * vx2) / 
                               (ball1.radius + ball2.radius);
                const v2Final = (vx2 * (ball2.radius - ball1.radius) + 2 * ball1.radius * vx1) / 
                               (ball1.radius + ball2.radius);
                
                // 更新速度
                ball1.dx = v1Final * cos - vy1 * sin;
                ball1.dy = vy1 * cos + v1Final * sin;
                ball2.dx = v2Final * cos - vy2 * sin;
                ball2.dy = vy2 * cos + v2Final * sin;
                
                // 防止球体重叠
                const overlap = ball1.radius + ball2.radius - distance;
                const moveX = overlap * cos * 0.5;
                const moveY = overlap * sin * 0.5;
                
                ball1.x -= moveX;
                ball1.y -= moveY;
                ball2.x += moveX;
                ball2.y += moveY;
            }
        }
    }
}

// 动画循环
function animatePhysics() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    updatePhysics();
    checkCollisions();
    drawBalls();
    
    requestAnimationFrame(animatePhysics);
}

animatePhysics();

5. 创意绘图 - 交互式涂鸦板

html 复制代码
<div>
    <input type="color" id="colorPicker" value="#000000">
    <input type="range" id="brushSize" min="1" max="50" value="5">
    <button id="clearBtn">清除</button>
    <button id="saveBtn">保存</button>
</div>
<canvas id="drawingCanvas" width="800" height="500"></canvas>
javascript 复制代码
const drawingCanvas = document.getElementById('drawingCanvas');
const ctx = drawingCanvas.getContext('2d');
const colorPicker = document.getElementById('colorPicker');
const brushSize = document.getElementById('brushSize');
const clearBtn = document.getElementById('clearBtn');
const saveBtn = document.getElementById('saveBtn');

let isDrawing = false;
let lastX = 0;
let lastY = 0;

// 设置画布背景为白色
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, drawingCanvas.width, drawingCanvas.height);
ctx.fillStyle = colorPicker.value;

// 绘图函数
function draw(e) {
    if (!isDrawing) return;
    
    ctx.strokeStyle = colorPicker.value;
    ctx.lineWidth = brushSize.value;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

// 鼠标事件
drawingCanvas.addEventListener('mousedown', (e) => {
    isDrawing = true;
    [lastX, lastY] = [e.offsetX, e.offsetY];
});

drawingCanvas.addEventListener('mousemove', draw);
drawingCanvas.addEventListener('mouseup', () => isDrawing = false);
drawingCanvas.addEventListener('mouseout', () => isDrawing = false);

// 触摸支持
drawingCanvas.addEventListener('touchstart', (e) => {
    e.preventDefault();
    const touch = e.touches[0];
    const rect = drawingCanvas.getBoundingClientRect();
    isDrawing = true;
    [lastX, lastY] = [touch.clientX - rect.left, touch.clientY - rect.top];
});

drawingCanvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    const touch = e.touches[0];
    const rect = drawingCanvas.getBoundingClientRect();
    draw({
        offsetX: touch.clientX - rect.left,
        offsetY: touch.clientY - rect.top
    });
});

drawingCanvas.addEventListener('touchend', () => isDrawing = false);

// 清除画布
clearBtn.addEventListener('click', () => {
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, drawingCanvas.width, drawingCanvas.height);
});

// 保存图像
saveBtn.addEventListener('click', () => {
    const link = document.createElement('a');
    link.download = 'drawing.png';
    link.href = drawingCanvas.toDataURL('image/png');
    link.click();
});

// 高级功能 - 橡皮擦
const eraserBtn = document.createElement('button');
eraserBtn.textContent = '橡皮擦';
document.querySelector('div').appendChild(eraserBtn);

eraserBtn.addEventListener('click', () => {
    colorPicker.value = '#ffffff';
});

// 高级功能 - 渐变笔刷
const gradientBtn = document.createElement('button');
gradientBtn.textContent = '渐变笔刷';
document.querySelector('div').appendChild(gradientBtn);

let useGradient = false;
gradientBtn.addEventListener('click', () => {
    useGradient = !useGradient;
    gradientBtn.style.backgroundColor = useGradient ? '#4CAF50' : '';
});

// 修改绘图函数以支持渐变
drawingCanvas.addEventListener('mousemove', function(e) {
    if (!isDrawing) return;
    
    if (useGradient) {
        const gradient = ctx.createLinearGradient(lastX, lastY, e.offsetX, e.offsetY);
        gradient.addColorStop(0, `hsl(${Math.random() * 360}, 100%, 50%)`);
        gradient.addColorStop(1, `hsl(${Math.random() * 360}, 100%, 50%)`);
        ctx.strokeStyle = gradient;
    } else {
        ctx.strokeStyle = colorPicker.value;
    }
    
    ctx.lineWidth = brushSize.value;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    
    [lastX, lastY] = [e.offsetX, e.offsetY];
});

这些实战项目展示了 Canvas 的强大功能,从简单的游戏开发到复杂的数据可视化和图像处理。你可以基于这些示例进行扩展,创建更复杂的应用。

相关推荐
千野竹之卫20 分钟前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
sunbyte20 分钟前
初识 Three.js:开启你的 Web 3D 世界 ✨
前端·javascript·3d
孙_华鹏2 小时前
手撸一个可以语音操作高德地图的AI智能体
前端·javascript·coze
拉不动的猪3 小时前
设计模式之--------工厂模式
前端·javascript·架构
Aphelios3803 小时前
TaskFlow开发日记 #1 - 原生JS实现智能Todo组件
java·开发语言·前端·javascript·ecmascript·todo
weixin_748877004 小时前
【在Node.js项目中引入TypeScript:提高开发效率及框架选型指南】
javascript·typescript·node.js
风中飘爻5 小时前
JavaScript:基本语法
开发语言·前端·javascript
Direct_Yang5 小时前
JavaScript性能优化(上)
开发语言·javascript·ecmascript
烛阴5 小时前
Express + Prisma + MySQL:一站式搭建高效 Node.js 后端服务
javascript·后端·express
涵信5 小时前
第二节:React 基础篇-受控组件 vs 非受控组件
前端·javascript·react.js