Javascript提高:一个彩色小球在画布边界内反弹并留下渐变轨迹-由Deepseek产生

以下是一个使用 requestAnimationFrame 实现的简单 canvas 动画:一个彩色小球在画布边界内反弹并留下渐变轨迹。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>requestAnimationFrame 简单动画 - 弹跳小球</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            background: linear-gradient(145deg, #1a2a3a 0%, #0f1a24 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
        }
        .container {
            background: rgba(0, 0, 0, 0.3);
            border-radius: 2rem;
            padding: 1rem;
            box-shadow: 0 20px 35px rgba(0, 0, 0, 0.4);
        }
        canvas {
            display: block;
            margin: 0 auto;
            border-radius: 1rem;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
            cursor: pointer;
        }
        .info {
            text-align: center;
            margin-top: 1rem;
            color: #eef4ff;
            font-weight: 500;
            text-shadow: 0 1px 2px black;
        }
        .info p {
            font-size: 0.9rem;
            background: rgba(0, 0, 0, 0.5);
            display: inline-block;
            padding: 0.3rem 1rem;
            border-radius: 2rem;
            backdrop-filter: blur(4px);
        }
        button {
            margin-top: 0.8rem;
            background: #ffb347;
            border: none;
            padding: 6px 18px;
            border-radius: 40px;
            font-weight: bold;
            cursor: pointer;
            transition: 0.2s;
            color: #2c3e2f;
            box-shadow: 0 2px 6px rgba(0,0,0,0.3);
        }
        button:hover {
            background: #ff9f1c;
            transform: scale(1.02);
        }
    </style>
</head>
<body>
<div>
    <div class="container">
        <canvas id="bouncingCanvas" width="800" height="500" style="width:800px; height:500px"></canvas>
        <div class="info">
            <p>⚡ 弹跳小球 | 渐变轨迹 | requestAnimationFrame 驱动</p>
            <button id="resetBtn">🔄 重置位置 / 随机速度</button>
        </div>
    </div>
</div>

<script>
    (function() {
        // ----- 获取 canvas 元素和上下文 -----
        const canvas = document.getElementById('bouncingCanvas');
        const ctx = canvas.getContext('2d');

        // ----- 动画参数 -----
        let ball = {
            x: canvas.width / 2,
            y: canvas.height / 2,
            radius: 24,
            vx: 3.2,      // 水平速度 (px/帧)
            vy: 2.7,      // 垂直速度
            color: '#ffaa44'
        };

        // 辅助变量:轨迹淡入淡出效果 (半透明填充背景实现残影/轨迹)
        // 为了避免无限拖尾,每帧用低透明度填充背景,制造运动模糊轨迹感
        
        // ----- 辅助函数:重置小球随机位置/速度 (但不超出边界)-----
        function resetBall() {
            ball.x = Math.random() * (canvas.width - 2 * ball.radius) + ball.radius;
            ball.y = Math.random() * (canvas.height - 2 * ball.radius) + ball.radius;
            // 随机速度 (-4 ~ 4 之间,避免为零)
            ball.vx = (Math.random() - 0.5) * 7;
            ball.vy = (Math.random() - 0.5) * 7;
            // 保证速度绝对值不太小,否则动画太慢
            if (Math.abs(ball.vx) < 0.8) ball.vx += (ball.vx >= 0 ? 0.9 : -0.9);
            if (Math.abs(ball.vy) < 0.8) ball.vy += (ball.vy >= 0 ? 0.9 : -0.9);
            // 限制最大速度 6
            ball.vx = Math.min(Math.max(ball.vx, -5.5), 5.5);
            ball.vy = Math.min(Math.max(ball.vy, -5.5), 5.5);
            // 动态改变一下颜色
            ball.color = `hsl(${Math.random() * 360}, 70%, 60%)`;
        }

        // ----- 更新小球位置 + 边界碰撞 (弹性反弹)-----
        function updatePosition() {
            // 更新坐标
            ball.x += ball.vx;
            ball.y += ball.vy;

            // 边界检测 (左右)
            if (ball.x + ball.radius >= canvas.width) {
                ball.x = canvas.width - ball.radius;
                ball.vx = -ball.vx;
                // 小彩蛋: 碰撞时微调颜色
                ball.color = `hsl(${Math.random() * 360}, 75%, 58%)`;
            } else if (ball.x - ball.radius <= 0) {
                ball.x = ball.radius;
                ball.vx = -ball.vx;
                ball.color = `hsl(${Math.random() * 360}, 75%, 58%)`;
            }

            // 边界检测 (上下)
            if (ball.y + ball.radius >= canvas.height) {
                ball.y = canvas.height - ball.radius;
                ball.vy = -ball.vy;
                ball.color = `hsl(${Math.random() * 360}, 75%, 58%)`;
            } else if (ball.y - ball.radius <= 0) {
                ball.y = ball.radius;
                ball.vy = -ball.vy;
                ball.color = `hsl(${Math.random() * 360}, 75%, 58%)`;
            }
        }

        // ----- 绘制画布 (背景 + 小球 + 光晕特效) -----
        function draw() {
            // 1. 拖尾/运动模糊效果: 用半透明黑填充全屏,让上一帧图像逐渐淡出
            //    注意: 这种方式会形成"光轨"感觉,非常平滑
            ctx.fillStyle = 'rgba(15, 26, 36, 0.27)';   // 透明度越低,轨迹越长
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // 2. 绘制额外装饰网格线 (增加科技感,可选,但不影响核心动画)
            ctx.save();
            ctx.globalCompositeOperation = 'lighter';
            ctx.beginPath();
            ctx.strokeStyle = 'rgba(100, 180, 255, 0.2)';
            ctx.lineWidth = 1;
            for (let i = 0; i < canvas.width; i += 40) {
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i, canvas.height);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(0, i % canvas.height);
                ctx.lineTo(canvas.width, i % canvas.height);
                ctx.stroke();
            }
            ctx.globalCompositeOperation = 'source-over';
            
            // 3. 绘制小球 (带高光、阴影)
            // 阴影 (增加立体感)
            ctx.shadowColor = 'rgba(0,0,0,0.5)';
            ctx.shadowBlur = 12;
            ctx.shadowOffsetX = 3;
            ctx.shadowOffsetY = 3;
            
            // 径向渐变 让小球更生动
            const grad = ctx.createRadialGradient(
                ball.x - 5, ball.y - 5, 5,
                ball.x, ball.y, ball.radius
            );
            grad.addColorStop(0, '#fff5c4');
            grad.addColorStop(0.6, ball.color);
            grad.addColorStop(1, `rgba(0,0,0,0.3)`);
            
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
            ctx.fillStyle = grad;
            ctx.fill();
            
            // 绘制高光小白点
            ctx.shadowBlur = 4;
            ctx.beginPath();
            ctx.arc(ball.x - 6, ball.y - 6, 6, 0, Math.PI * 2);
            ctx.fillStyle = 'rgba(255, 255, 245, 0.7)';
            ctx.fill();
            
            // 重置阴影
            ctx.shadowBlur = 0;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
            
            // 额外描边轮廓
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius - 1, 0, Math.PI * 2);
            ctx.strokeStyle = 'rgba(255,210,90,0.7)';
            ctx.lineWidth = 1.8;
            ctx.stroke();
            ctx.restore();
        }
        
        // ----- 主动画循环 (使用 requestAnimationFrame) -----
        let animationId = null;
        
        function animate() {
            // 1. 更新物理(位置、碰撞)
            updatePosition();
            
            // 2. 绘制当前帧
            draw();
            
            // 3. 注册下一帧
            animationId = requestAnimationFrame(animate);
        }
        
        // ----- 优雅的启动与重置控制 -----
        function startAnimation() {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }
            animate();
        }
        
        // 重置按钮交互 (重置位置和速度,同时改变颜色)
        const resetBtn = document.getElementById('resetBtn');
        resetBtn.addEventListener('click', () => {
            resetBall();
            // 可选:额外清屏+闪烁效果 (瞬间clear)
            ctx.fillStyle = '#0f1a24';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            draw(); // 立即绘制重置后的位置让画面没有跳跃感
        });
        
        // 初始化随机速度一次(让每次刷新不同)
        resetBall();
        // 确保一开始画布背景
        ctx.fillStyle = '#0f1a24';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        draw();        // 先画一帧避免白屏
        startAnimation();
        
        // 可选:页面可见性变化时,无需额外操作,但 requestAnimationFrame 自动暂停节省资源
        // 防止页面关闭后动画继续 (不需要额外代码,浏览器已做优化)
        // 窗口resize时 canvas尺寸固定为800x500, 无需处理
    })();
</script>
</body>
</html>

动画说明

  1. 核心驱动requestAnimationFrame 循环调用 animate() 函数,每帧更新小球位置并重绘。
  2. 物理效果 :小球拥有速度 vx / vy,碰到边界时速度反向(弹性碰撞),同时随机改变颜色增加趣味。
  3. 视觉风格
    • 半透明背景填充制造"拖尾/轨迹"效果,形成运动模糊感。
    • 径向渐变 + 高光让小球更立体。
    • 半透明网格线增强科技感。
  4. 交互:点击重置按钮可随机重置小球位置、速度以及颜色。

你可以直接复制代码到 .html 文件中并用浏览器打开,即可看到流畅的动画效果。

相关推荐
Z文的博客1 小时前
【避坑实录】Qt 4.8.6 + Paho MQTT C客户端 + OpenSSL静态链接的血泪史
c语言·开发语言·qt·嵌入式linux
神仙别闹1 小时前
基于Python实现上下消化道病历分类
开发语言·python·分类
一行代码一行诗++1 小时前
转义字符和语句
c语言·开发语言·算法
sinat_255487811 小时前
数组·学习笔记
java·javascript·笔记
逻辑驱动的ken1 小时前
Java高频面试考点场景题16
java·开发语言·面试·职场和发展·求职招聘
xingpanvip1 小时前
星盘接口开发文档:天象盘接口指南
android·开发语言·python·php·lua
DukeMr.Lee2 小时前
有声书实现
java·开发语言
今夕资源网2 小时前
Visual C++运行库合集 V104.0 一个github免费开源的项目VisualCppRedist AIO
开发语言·c++·dll修复工具·dll修复·运行库·修复软件
syagain_zsx2 小时前
剖析“继承”,清晰易懂
开发语言·c++