如何使用 HTML5 的 Canvas + JavaScript 实现炫酷的游戏得分特效?

文章目录

    • [🎬 动画效果抢先看](#🎬 动画效果抢先看)
    • [🏗️ 舞台搭建:Canvas基础配置](#🏗️ 舞台搭建:Canvas基础配置)
    • [🎨 分层绘制:让特效更有层次感](#🎨 分层绘制:让特效更有层次感)
    • [✨ 背景星光:Canvas之外的开胃小菜](#✨ 背景星光:Canvas之外的开胃小菜)
    • [🔤 会跳舞的得分文字:文本绘制技巧](#🔤 会跳舞的得分文字:文本绘制技巧)
    • [🎇 粒子特效:让得分飞一会儿](#🎇 粒子特效:让得分飞一会儿)
    • [🌟 光晕效果:给特效加点"光"](#🌟 光晕效果:给特效加点"光")
    • [🚀 动画循环:让一切动起来的秘密](#🚀 动画循环:让一切动起来的秘密)
    • [🎮 交互反馈:按钮点击的魔法](#🎮 交互反馈:按钮点击的魔法)
    • [🎓 总结](#🎓 总结)

🎬 动画效果抢先看

【资源免费】因为这里上传文件大小有限制,所以不是很清晰,大家可以下载顶部免费资源,自己本地双击打开html文件即可

🏗️ 舞台搭建:Canvas基础配置

任何Canvas项目的第一步都是搭建基础环境,让我们来看看代码中是如何做的:

html 复制代码
<canvas id="score-canvas"></canvas>
<canvas id="glow-canvas"></canvas>

这两个canvas元素就是我们绘制特效的舞台。接下来在JavaScript中获取它们并设置上下文:

javascript 复制代码
// Canvas 初始化
const scoreCanvas = document.getElementById('score-canvas');
const scoreCtx = scoreCanvas.getContext('2d');
const glowCanvas = document.getElementById('glow-canvas');
const glowCtx = glowCanvas.getContext('2d');

getContext('2d')方法是获取Canvas 2D渲染上下文,这是所有2D绘图操作的入口点,提供了各种绘制图形的方法。

为了让Canvas适应不同屏幕大小,还需要处理窗口大小变化:

javascript 复制代码
// 适配窗口大小
function resizeCanvas() {
    scoreCanvas.width = window.innerWidth;
    scoreCanvas.height = window.innerHeight;
    glowCanvas.width = window.innerWidth;
    glowCanvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);

这里有个小知识点:Canvas的widthheight属性与CSS中的width和height不同,它们决定了Canvas的实际像素尺寸,而CSS只是控制其在页面上的显示大小。如果只设置CSS而不设置Canvas自身的width和height,绘制的内容会被拉伸变形。

🎨 分层绘制:让特效更有层次感

这个特效最巧妙的一点是使用了两层Canvas:

css 复制代码
#score-canvas {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    z-index: 0; /* 低于按钮z-index=1,不遮挡按钮 */
}

#glow-canvas {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    pointer-events: none;
    z-index: -1; /* 最底层光晕 */
}

这种分层策略在复杂Canvas动画中非常常见,有以下几个好处:

  1. 性能优化:不同特效更新频率不同,分层可以避免不必要的重绘
  2. 视觉层次感:通过z-index控制不同元素的显示层级
  3. 简化逻辑:将不同类型的特效分离到不同画布,便于管理

pointer-events: none这个CSS属性也很有用,它让Canvas不会响应鼠标事件,确保下方的按钮可以正常被点击。

✨ 背景星光:Canvas之外的开胃小菜

在正式进入Canvas特效之前,代码中先用普通的HTML元素创建了背景星光效果:

javascript 复制代码
// 背景星光生成
const stars = document.querySelector('.stars');
for (let i = 0; i < 150; i++) {
    const star = document.createElement('div');
    star.style.position = 'absolute';
    star.style.width = `${Math.random() * 3 + 1}px`;
    star.style.height = star.style.width;
    star.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
    star.style.borderRadius = '50%';
    star.style.left = `${Math.random() * 100}vw`;
    star.style.top = `${Math.random() * 100}vh`;
    star.style.boxShadow = '0 0 10px rgba(255, 255, 255, 0.5)';
    star.style.animation = `twinkle ${Math.random() * 3 + 2}s infinite alternate`;
    stars.appendChild(star);
}

这段代码虽然没有使用Canvas,但是为我们展示了一种创建随机分布元素的思路,这种思路在后面的粒子系统中也会用到。每个星星都有随机的大小、位置和闪烁频率,营造出深邃太空的感觉。

🔤 会跳舞的得分文字:文本绘制技巧

得分文字是这个特效的核心元素之一,让我们看看ScoreText类是如何实现的:

javascript 复制代码
class ScoreText {
    constructor(x, y, score) {
        this.x = x;
        this.y = y;
        this.score = score;
        // 根据分数设置不同大小
        this.baseSize = score >= 1000 ? 56 : score >= 500 ? 48 : 40;
        this.size = this.baseSize * 1.8; // 初始放大效果
        this.opacity = 1;
        this.speedY = -4; // 向上移动
        this.life = 70;
        this.pulse = 0;
        this.swing = 0;
        
        // 创建文本渐变
        this.gradient = scoreCtx.createLinearGradient(0, 0, 0, this.baseSize);
        if (score === 100) {
            this.gradient.addColorStop(0, 'rgba(139, 172, 248, 1)');
            this.gradient.addColorStop(1, 'rgba(59, 130, 246, 1)');
        } else if (score === 500) {
            this.gradient.addColorStop(0, 'rgba(110, 231, 183, 1)');
            this.gradient.addColorStop(1, 'rgba(16, 185, 129, 1)');
        } else {
            this.gradient.addColorStop(0, 'rgba(252, 211, 77, 1)');
            this.gradient.addColorStop(1, 'rgba(245, 158, 11, 1)');
        }
    }
    
    update() {
        // 更新位置、大小和透明度
        this.y += this.speedY;
        this.life--;
        this.pulse += 0.2;
        this.swing = Math.sin(this.pulse) * 2; // 利用正弦函数实现摇摆效果
        
        // 缩放动画
        this.size = this.size > this.baseSize 
            ? this.size - this.baseSize * 0.05 
            : this.baseSize + Math.sin(this.pulse) * 3;
        
        this.opacity = this.life > 40 ? 1 : this.life / 40;
    }
    
    draw() {
        scoreCtx.save();
        scoreCtx.translate(this.x + this.swing, this.y);
        
        // 设置阴影效果增强立体感
        scoreCtx.shadowColor = this.score === 100 ? 'rgba(59, 130, 246, 0.8)' : 
                              this.score === 500 ? 'rgba(16, 185, 129, 0.8)' : 
                              'rgba(245, 158, 11, 0.8)';
        scoreCtx.shadowBlur = 25;
        
        // 绘制渐变文本
        scoreCtx.font = `bold ${this.size}px Arial`;
        scoreCtx.fillStyle = this.gradient;
        scoreCtx.textAlign = 'center';
        scoreCtx.textBaseline = 'middle';
        scoreCtx.fillText(`+${this.score}`, 0, 0);
        
        // 文本描边增强效果
        scoreCtx.strokeStyle = `rgba(255, 255, 255, ${this.opacity * 0.9})`;
        scoreCtx.lineWidth = 3;
        scoreCtx.strokeText(`+${this.score}`, 0, 0);
        
        scoreCtx.restore();
    }
}

这里用到了几个重要的Canvas文本绘制技巧:

  1. 渐变文本 :使用createLinearGradient()创建线性渐变,让文字更有质感
  2. 文本阴影 :通过shadowColorshadowBlur属性给文字添加发光效果
  3. 文本描边 :结合fillText()strokeText()让文字更醒目
  4. 文本对齐 :使用textAligntextBaseline精确控制文本位置

同时,通过update()方法实现了文字的多种动画效果:初始放大、向上移动、左右摇摆和逐渐消失,让文字看起来栩栩如生。

🎇 粒子特效:让得分飞一会儿

粒子系统是游戏特效中常用的技术,这个案例中实现了两种粒子:普通得分粒子和1000分专属的爆发粒子。

先看普通的ScoreParticle类:

javascript 复制代码
class ScoreParticle {
    constructor(x, y, score) {
        this.x = x;
        this.y = y;
        this.size = Math.random() * 6 + 2;
        this.shape = Math.random() > 0.5 ? 'circle' : 'triangle'; // 随机形状
        this.rotation = Math.random() * Math.PI * 2;
        this.rotateSpeed = (Math.random() - 0.5) * 0.2;
        
        // 根据分数设置不同颜色
        this.color = score === 100 
            ? `rgba(${99 + Math.random() * 50}, ${102 + Math.random() * 50}, ${241 + Math.random() * 14}, ${Math.random() * 0.7 + 0.5})`
            : score === 500 
                ? `rgba(${16 + Math.random() * 50}, ${185 + Math.random() * 50}, ${129 + Math.random() * 26}, ${Math.random() * 0.7 + 0.5})`
                : `rgba(${245 + Math.random() * 10}, ${158 + Math.random() * 50}, ${11 + Math.random() * 44}, ${Math.random() * 0.7 + 0.5})`;
        
        // 随机运动方向
        const angle = Math.random() * Math.PI * 2;
        const speed = score >= 1000 ? Math.random() * 5 + 2 : Math.random() * 4 + 1;
        this.vx = Math.cos(angle) * speed;
        this.vy = Math.sin(angle) * speed - 3;
        this.arc = score >= 500 ? (Math.random() - 0.5) * 0.1 : 0; // 高分曲线运动
        this.gravity = 0.05;
        this.drag = 0.985;
        this.life = score >= 1000 ? Math.random() * 40 + 30 : Math.random() * 30 + 20;
    }
    
    update() {
        // 应用物理效果
        this.vx += Math.sin(this.y * 0.05) * this.arc; // 曲线偏移
        this.vx *= this.drag; // 阻力
        this.vy *= this.drag;
        this.vy += this.gravity; // 重力
        
        this.x += this.vx;
        this.y += this.vy;
        this.rotation += this.rotateSpeed;
        
        this.life--;
        this.opacity = this.life / (this.life > 50 ? 70 : 50);
        this.size *= 0.96; // 逐渐缩小
    }
    
    draw() {
        scoreCtx.save();
        scoreCtx.translate(this.x, this.y);
        scoreCtx.rotate(this.rotation);
        
        // 粒子发光效果
        scoreCtx.shadowColor = this.color;
        scoreCtx.shadowBlur = 12;
        
        if (this.shape === 'circle') {
            // 绘制圆形粒子
            scoreCtx.beginPath();
            scoreCtx.arc(0, 0, this.size, 0, Math.PI * 2);
            scoreCtx.fillStyle = this.color.replace(/[^,]+(?=\))/, this.opacity);
            scoreCtx.fill();
        } else {
            // 绘制三角形粒子
            scoreCtx.beginPath();
            scoreCtx.moveTo(0, -this.size);
            scoreCtx.lineTo(this.size * 0.866, this.size * 0.5);
            scoreCtx.lineTo(-this.size * 0.866, this.size * 0.5);
            scoreCtx.closePath();
            scoreCtx.fillStyle = this.color.replace(/[^,]+(?=\))/, this.opacity);
            scoreCtx.fill();
        }
        
        scoreCtx.restore();
    }
}

这个粒子类展示了Canvas绘制基本形状的方法:

  1. 绘制圆形 :使用arc()方法,参数分别是圆心x、y,半径,起始角度和结束角度
  2. 绘制多边形 :使用moveTo()lineTo()方法定义路径,最后用closePath()闭合

更重要的是,它实现了简单的物理系统:

  • 重力效果:通过逐渐增加y方向速度实现
  • 阻力效果:通过乘以一个小于1的值逐渐减小速度
  • 曲线运动:通过正弦函数计算水平方向的偏移

对于1000分的特殊效果,代码还实现了BurstParticle类,提供更强烈的视觉冲击:

javascript 复制代码
class BurstParticle {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.size = Math.random() * 8 + 3;
        this.color = `rgba(${255}, ${Math.random() * 200 + 55}, ${Math.random() * 50}, ${Math.random() * 0.8 + 0.4})`;
        const angle = Math.random() * Math.PI * 2;
        const speed = Math.random() * 10 + 5; // 更快的速度
        this.vx = Math.cos(angle) * speed;
        this.vy = Math.sin(angle) * speed;
        this.life = Math.random() * 20 + 15;
    }
    
    // update和draw方法类似,这里省略...
}

🌟 光晕效果:给特效加点"光"

光晕效果是提升视觉冲击力的重要手段,代码中使用单独的Canvas层来实现:

javascript 复制代码
class GlowEffect {
    constructor(x, y, score) {
        this.x = x;
        this.y = y;
        // 根据分数设置不同初始半径
        this.radius = score >= 1000 ? 120 : score >= 500 ? 80 : 50;
        this.opacity = 0.8;
        // 根据分数设置不同颜色
        this.color = score === 100 
            ? `rgba(59, 130, 246, ${this.opacity})`
            : score === 500 
                ? `rgba(16, 185, 129, ${this.opacity})`
                : `rgba(245, 158, 11, ${this.opacity})`;
    }
    
    update() {
        this.radius += 3; // 逐渐扩大
        this.opacity *= 0.95; // 逐渐透明
    }
    
    draw() {
        glowCtx.save();
        glowCtx.beginPath();
        glowCtx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        glowCtx.fillStyle = this.color.replace(/[^,]+(?=\))/, this.opacity);
        glowCtx.filter = 'blur(30px)'; // 模糊效果实现光晕
        glowCtx.fill();
        glowCtx.filter = 'none'; // 重置滤镜
        glowCtx.restore();
    }
}

这里的关键技术是使用filter: 'blur(30px)'来实现模糊效果,模拟光线扩散的感觉。同时,通过让光晕逐渐扩大并降低透明度,营造出一种爆炸后光芒扩散的效果。

值得注意的是,绘制完成后要使用glowCtx.filter = 'none'重置滤镜,避免影响后续绘制的元素。

🚀 动画循环:让一切动起来的秘密

所有静态的元素和效果,都需要通过动画循环才能变成生动的特效:

javascript 复制代码
function animate() {
    // 半透明清除,保留拖影效果
    scoreCtx.fillStyle = 'rgba(0, 0, 0, 0.08)';
    scoreCtx.fillRect(0, 0, scoreCanvas.width, scoreCanvas.height);
    glowCtx.fillStyle = 'rgba(0, 0, 0, 0.15)';
    glowCtx.fillRect(0, 0, glowCanvas.width, glowCanvas.height);
    
    // 更新并绘制所有特效元素
    glowEffects.forEach(glow => { glow.update(); glow.draw(); });
    scoreTexts.forEach(text => { text.update(); text.draw(); });
    scoreParticles.forEach(p => { p.update(); p.draw(); });
    burstParticles.forEach(p => { p.update(); p.draw(); });
    
    // 移除生命周期结束的元素,优化性能
    glowEffects.filter(glow => glow.opacity < 0.05).forEach(glow => {
        glowEffects.splice(glowEffects.indexOf(glow), 1);
    });
    // 其他元素的清理...
    
    requestAnimationFrame(animate);
}
animate(); // 启动动画循环

这个动画循环使用了requestAnimationFrame方法,这是现代浏览器推荐的动画API,它会根据浏览器的刷新频率自动调整调用频率,通常是每秒60次,比使用setInterval更高效。

循环中做了三件重要的事情:

  1. 清除画布:使用半透明的黑色填充整个画布,这种方式可以保留上一帧的部分痕迹,产生拖影效果
  2. 更新和绘制 :调用所有特效元素的update()方法更新状态,再调用draw()方法绘制
  3. 清理过期元素:移除那些已经不可见或生命周期结束的元素,避免性能损耗

🎮 交互反馈:按钮点击的魔法

最后,我们来看看如何将这些特效与用户交互结合起来:

javascript 复制代码
// 按钮点击触发特效
scoreBtns.forEach(btn => {
    btn.addEventListener('click', (e) => {
        const score = parseInt(btn.dataset.score);
        // 获取按钮中心位置
        const rect = btn.getBoundingClientRect();
        const x = rect.left + rect.width / 2;
        const y = rect.top + rect.height / 2;
        
        // 创建各种特效元素
        glowEffects.push(new GlowEffect(x, y, score));
        scoreTexts.push(new ScoreText(x, y, score));
        
        // 根据分数创建不同数量的粒子
        const particleCount = score === 100 ? 60 : score === 500 ? 100 : 150;
        for (let i = 0; i < particleCount; i++) {
            scoreParticles.push(new ScoreParticle(x, y, score));
        }
        
        // 1000分专属特效
        if (score === 1000) {
            // 爆发粒子
            for (let i = 0; i < 80; i++) {
                burstParticles.push(new BurstParticle(x, y));
            }
            // 屏幕闪烁效果
            document.body.style.backgroundColor = 'rgba(255, 220, 0, 0.1)';
            setTimeout(() => { document.body.style.backgroundColor = ''; }, 100);
            // 按钮震动
            btn.classList.add('btn-shake');
            setTimeout(() => { btn.classList.remove('btn-shake'); }, 400);
        }
        
        // 按钮点击反馈动画
        btn.style.transform = 'scale(0.9)';
        setTimeout(() => {
            btn.style.transform = 'scale(1.05)';
            setTimeout(() => { btn.style.transform = 'scale(1)'; }, 100);
        }, 100);
    });
});

这段代码实现了点击按钮时的完整交互逻辑:

  1. 确定位置 :使用getBoundingClientRect()获取按钮在视口中的位置,计算出中心点作为特效的发射点
  2. 创建特效:根据不同的分数值,创建相应的光晕、文字和粒子特效
  3. 特殊处理:为1000分添加额外的爆发粒子、屏幕闪烁和按钮震动效果
  4. 按钮反馈:通过CSS transform实现按钮的按压反馈动画

这种根据不同分数值提供不同强度特效的做法,增强了游戏的成就感和趣味性。

🎓 总结

这篇文章对应的资源可以直接免费下载,效果也是简单实现,主要是为大家提供一个思路,我的Canvas实战专栏中会有更加精细的Canvas3D效果的动画展现给大家,如果大家又想要的动画效果,欢迎大家在评论区留言,我会不定时实现评论区粉丝的期望动画效果,欢迎大家关注✨✨✨

相关推荐
q***06291 小时前
解决 Tomcat 跨域问题 - Tomcat 配置静态文件和 Java Web 服务(Spring MVC Springboot)同时允许跨域
java·前端·spring
张丶大帅1 小时前
JS案例合集
开发语言·javascript·笔记
木易 士心1 小时前
深入理解 CSS 中的 !important
前端·css
行走的陀螺仪2 小时前
GitLab CI/CD 完整教学指南
前端·ci/cd·gitlab·团队开发·自动化测试部署
谢尔登2 小时前
Webpack高级之常用配置项
前端·webpack·node.js
helloyangkl2 小时前
前端——不同环境下配置env
前端·javascript·react.js
竹秋…2 小时前
webpack搭建react开发环境
前端·react.js·webpack
以明志、2 小时前
并行与并发
前端·数据库·c#
提笔了无痕2 小时前
go web开发表单知识及表单处理详解
前端·后端·golang·web