Python跨年烟花代码实战:5分钟打造炫酷网页特效(附完整源码)
Python跨年烟花代码实战:5分钟打造炫酷网页特效(附完整源码)
又到了年末,想给你的个人网站、项目演示或者节日贺卡加点特别的氛围吗?用Python在网页上实现一场绚丽的跨年烟花秀,听起来是不是既酷炫又有技术范儿?很多朋友觉得网页动画和图形编程门槛很高,需要精通JavaScript和Canvas。但今天,我想分享一个不一样的思路:用Python作为核心逻辑引擎,结合一点前端“胶水”代码,快速生成动态、可交互的烟花特效。这种方法不仅能让Python开发者在自己熟悉的领域大展身手,还能将成果轻松嵌入任何网页,效果丝毫不输纯前端实现。
这篇文章就是为你准备的,无论你是想为个人项目增添节日气氛的开发者,还是希望理解图形编程基本原理的初学者。我们将避开复杂的底层图形API,从一个高度封装、易于理解的“粒子系统”模型入手。我会提供一套完整的、可运行的代码,并带你一步步拆解其核心模块,从烟花的发射、爆炸、粒子运动到颜色与轨迹的个性化定制。你不仅能获得一个即拿即用的特效方案,更能掌握一套可复用的动态图形创作方法论。
1. 环境准备与项目初始化
在开始编写烟花代码之前,我们需要搭建一个轻量级的开发环境。核心思路是:使用Python处理动画逻辑和粒子数据,然后通过一个简单的HTML页面配合JavaScript的Canvas API进行渲染。这种前后端分离的方式,让Python负责复杂的计算,前端负责高效的绘制,各司其职。
首先,确保你的电脑上安装了Python 3.6或更高版本。我们主要会用到Python的标准库,因此无需安装额外的包。项目的目录结构可以这样规划:
new_year_fireworks/
├── fireworks_logic.py # Python核心逻辑:粒子系统模拟
├── index.html # 主页面,包含Canvas和渲染脚本
├── render.js # 负责与Python逻辑交互并绘制
└── server.py # 一个简单的HTTP服务器,用于本地预览
我们先从最核心的Python逻辑文件开始。创建一个名为 fireworks_logic.py 的文件。这里我们将定义一个粒子(Particle) 类和烟花(Firework) 类,它们是整个特效的基石。
提示:粒子系统是计算机图形学中模拟模糊现象(如烟、火、水)的经典技术,其核心是将整体效果分解为无数个微小、独立的粒子,并定义每个粒子的生命周期和行为规则。
# fireworks_logic.py
import random
import math
import json
import time
class Particle:
"""代表单个发光粒子的类"""
def __init__(self, x, y, vx, vy, color, lifetime=100):
self.x = x
self.y = y
self.vx = vx # x轴速度
self.vy = vy # y轴速度
self.color = color # RGB颜色元组,例如 (255, 100, 50)
self.initial_lifetime = lifetime
self.lifetime = lifetime # 粒子存活时间(帧数)
self.gravity = 0.1 # 模拟重力加速度
self.drag = 0.97 # 空气阻力,使速度逐渐衰减
def update(self):
"""更新粒子状态:位置、速度、生命周期"""
self.x += self.vx
self.y += self.vy
self.vy += self.gravity # 重力影响y轴速度
# 空气阻力模拟
self.vx *= self.drag
self.vy *= self.drag
self.lifetime -= 1
def is_alive(self):
"""判断粒子是否还在生命周期内"""
return self.lifetime > 0
def get_render_data(self):
"""获取用于前端渲染的数据,包括位置、颜色和透明度"""
alpha = self.lifetime / self.initial_lifetime # 计算透明度衰减
return {
'x': self.x,
'y': self.y,
'color': self.color,
'alpha': alpha
}
接下来是烟花类,它负责在到达顶点时“爆炸”,生成一群粒子。
class Firework:
"""代表一枚上升后爆炸的烟花"""
def __init__(self, start_x, start_y, target_height=None):
self.x = start_x
self.y = start_y
self.start_y = start_y
# 随机设定一个目标爆炸高度,在画布上半部分
self.target_y = target_height if target_height else random.randint(50, 300)
# 初始向上速度,随机赋予一些水平偏移
self.vx = random.uniform(-1.5, 1.5)
self.vy = -random.uniform(10, 13) # 负值表示向上
self.gravity = 0.2
self.exploded = False
self.particles = [] # 爆炸后产生的粒子列表
# 随机生成一个鲜艳的爆炸主色
self.explosion_color = (
random.randint(200, 255),
random.randint(50, 200),
random.randint(50, 200)
)
def update(self):
"""更新烟花状态:上升阶段或爆炸后的粒子更新"""
if not self.exploded:
# 上升阶段
self.x += self.vx
self.y += self.vy
self.vy += self.gravity
# 判断是否到达爆炸点
if self.vy >= 0: # 速度由负转正,表示到达最高点开始下落
self.explode()
else:
# 爆炸后,更新所有粒子
for particle in self.particles[:]: # 使用切片创建副本以便安全删除
particle.update()
if not particle.is_alive():
self.particles.remove(particle)
def explode(self):
"""执行爆炸,生成大量粒子"""
self.exploded = True
particle_count = random.randint(80, 150)
for _ in range(particle_count):
# 随机角度,均匀分布
angle = random.uniform(0, math.pi * 2)
# 随机速度,模拟爆炸的冲击力
speed = random.uniform(2, 8)
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed
# 粒子颜色可以在主色基础上进行微调,增加层次感
color_variation = random.randint(-30, 30)
color = (
min(255, max(0, self.explosion_color[0] + color_variation)),
min(255, max(0, self.explosion_color[1] + color_variation)),
min(255, max(0, self.explosion_color[2] + color_variation))
)
lifetime = random.randint(60, 120)
self.particles.append(
Particle(self.x, self.y, vx, vy, color, lifetime)
)
def is_done(self):
"""判断烟花是否已完成生命周期(爆炸且所有粒子消失)"""
return self.exploded and len(self.particles) == 0
def get_render_data(self):
"""获取渲染数据:未爆炸时返回烟花本体,爆炸后返回所有粒子"""
if not self.exploded:
return {
'type': 'rocket',
'x': self.x,
'y': self.y,
'color': (255, 255, 200) # 上升阶段的尾焰颜色
}
else:
return {
'type': 'explosion',
'particles': [p.get_render_data() for p in self.particles]
}
最后,我们需要一个管理器来统筹所有的烟花发射和循环。
class FireworksManager:
"""烟花效果管理器,控制多枚烟花的生成与循环"""
def __init__(self, canvas_width=800, canvas_height=600):
self.canvas_width = canvas_width
self.canvas_height = canvas_height
self.fireworks = []
self.next_launch_time = time.time()
self.launch_interval = 0.8 # 发射间隔(秒)
def update(self):
"""更新所有烟花状态,并可能发射新的烟花"""
current_time = time.time()
# 定时发射新烟花
if current_time > self.next_launch_time and len(self.fireworks) < 15: # 控制同时存在的烟花数量
self.launch_firework()
self.next_launch_time = current_time + self.launch_interval
# 更新每枚烟花
for firework in self.fireworks[:]:
firework.update()
if firework.is_done():
self.fireworks.remove(firework)
def launch_firework(self):
"""从底部随机位置发射一枚新烟花"""
start_x = random.randint(100, self.canvas_width - 100)
start_y = self.canvas_height
self.fireworks.append(Firework(start_x, start_y))
def get_all_render_data(self):
"""获取所有烟花和粒子的渲染数据,用于发送给前端"""
render_list = []
for fw in self.fireworks:
render_list.append(fw.get_render_data())
return render_list
def generate_frame_data(self):
"""生成一帧的数据,通常由主循环调用"""
self.update()
return self.get_all_render_data()
# 一个简单的模拟循环,用于测试逻辑
if __name__ == "__main__":
manager = FireworksManager()
for i in range(200): # 模拟200帧
data = manager.generate_frame_data()
print(f"Frame {i}: {len(data)} objects")
time.sleep(0.033) # 模拟约30FPS
至此,我们的核心Python逻辑就完成了。你可以直接运行这个文件,在控制台看到每一帧的物体数量输出,这验证了我们的粒子系统正在正确模拟。接下来,我们需要让这些数据“动”起来,在网页上看到图形。
2. 构建前端渲染引擎
有了强大的Python后端逻辑,前端的工作就变得清晰而简单:定期获取Python计算出的粒子数据,并用Canvas API将它们画出来。我们将创建一个HTML文件和一个JavaScript文件来实现通信与渲染。
首先创建 index.html,这是我们的主页面。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python驱动 - 跨年烟花秀</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #0a0a20; /* 深蓝色背景模拟夜空 */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: sans-serif;
}
#container {
position: relative;
box-shadow: 0 0 40px rgba(0, 150, 255, 0.3);
border-radius: 8px;
overflow: hidden;
}
#title {
position: absolute;
top: 20px;
width: 100%;
text-align: center;
color: #fff;
z-index: 10;
text-shadow: 0 0 10px #00a8ff;
font-size: 2.5em;
pointer-events: none;
}
#canvas {
display: block;
background: radial-gradient(ellipse at center, #0c1a3a 0%, #050814 100%);
}
#controls {
position: absolute;
bottom: 20px;
left: 20px;
color: #ccc;
background: rgba(0, 0, 0, 0.5);
padding: 15px;
border-radius: 10px;
font-size: 0.9em;
}
button {
background: linear-gradient(to bottom, #4a6fe3, #2a4bc1);
color: white;
border: none;
padding: 8px 15px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background: linear-gradient(to bottom, #5b7ef3, #3a5bd1);
}
</style>
</head>
<body>
<div id="container">
<div id="title">Happy New Year 2025</div>
<canvas id="canvas"></canvas>
<div id="controls">
<p>烟花特效控制面板</p>
<button id="btnMore">发射更多</button>
<button id="btnPause">暂停/继续</button>
<label for="colorMode">色彩模式: </label>
<select id="colorMode">
<option value="vibrant">鲜艳模式</option>
<option value="pastel">柔和模式</option>
<option value="mono">单色模式</option>
</select>
<p>当前粒子数: <span id="particleCount">0</span></p>
</div>
</div>
<script src="render.js"></script>
</body>
</html>
这个HTML页面定义了一个全屏的Canvas画布、一个标题、一个简单的控制面板,并引入了我们即将编写的 render.js。现在,让我们看看如何用JavaScript连接Python逻辑并实现绘制。
创建 render.js 文件。这里的关键是使用 fetch API 定期从我们的Python后端(通过一个微型服务器)获取最新的粒子数据。
// render.js
class FireworksRenderer {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
this.particleData = []; // 存储从后端获取的数据
this.animationId = null;
this.isPaused = false;
this.colorMode = 'vibrant';
// 初始化UI事件监听
this.initControls();
// 开始动画循环
this.animate();
// 开始从后端获取数据
this.startDataFetching();
}
resizeCanvas() {
// 使Canvas充满其容器,并保持高清渲染
const container = this.canvas.parentElement;
this.canvas.width = container.clientWidth;
this.canvas.height = container.clientHeight;
this.width = this.canvas.width;
this.height = this.canvas.height;
}
initControls() {
document.getElementById('btnMore').addEventListener('click', () => {
// 向后端发送指令,请求立即发射一枚烟花
fetch('/launch', { method: 'POST' })
.then(response => response.json())
.then(data => console.log('手动发射成功', data));
});
document.getElementById('btnPause').addEventListener('click', () => {
this.isPaused = !this.isPaused;
document.getElementById('btnPause').textContent = this.isPaused ? '继续' : '暂停';
});
document.getElementById('colorMode').addEventListener('change', (e) => {
this.colorMode = e.target.value;
});
}
async fetchParticleData() {
try {
const response = await fetch('/frame');
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
this.particleData = data;
// 更新UI上的粒子计数
let totalParticles = 0;
data.forEach(item => {
if (item.type === 'explosion') {
totalParticles += item.particles.length;
}
});
document.getElementById('particleCount').textContent = totalParticles;
} catch (error) {
console.warn('获取粒子数据失败,使用模拟数据:', error);
// 如果后端未就绪,可以在这里提供一些模拟数据用于前端单独测试
this.generateMockData();
}
}
generateMockData() {
// 简单的模拟数据生成,用于独立测试前端渲染效果
this.particleData = [];
// ... 模拟数据生成逻辑(此处省略以保持简洁)
}
startDataFetching() {
// 每30毫秒从后端获取一次数据(约33FPS)
setInterval(() => {
if (!this.isPaused) {
this.fetchParticleData();
}
}, 30);
}
applyColorMode(r, g, b) {
// 根据选择的色彩模式调整颜色
switch (this.colorMode) {
case 'pastel':
// 柔和模式:降低饱和度,提高亮度
return `rgb(${r+50}, ${g+50}, ${b+50})`;
case 'mono':
// 单色模式:统一为金色调
const avg = (r + g + b) / 3;
return `rgb(${avg}, ${avg*0.8}, ${avg*0.2})`;
case 'vibrant':
default:
// 鲜艳模式:原色输出
return `rgb(${r}, ${g}, ${b})`;
}
}
drawParticle(x, y, color, alpha, size = 3) {
this.ctx.beginPath();
// 使用径向渐变模拟粒子的光晕效果
const gradient = this.ctx.createRadialGradient(x, y, 0, x, y, size * 2);
gradient.addColorStop(0, `rgba(${color}, ${alpha})`);
gradient.addColorStop(1, `rgba(${color}, 0)`);
this.ctx.fillStyle = gradient;
this.ctx.arc(x, y, size, 0, Math.PI * 2);
this.ctx.fill();
}
drawRocket(x, y) {
// 绘制上升中的烟花(火箭)
this.ctx.beginPath();
const rocketGradient = this.ctx.createLinearGradient(x, y, x, y + 15);
rocketGradient.addColorStop(0, 'rgba(255, 255, 200, 0.9)');
rocketGradient.addColorStop(1, 'rgba(255, 100, 50, 0)');
this.ctx.fillStyle = rocketGradient;
// 绘制一个小的三角形作为火箭头
this.ctx.moveTo(x, y);
this.ctx.lineTo(x - 2, y + 8);
this.ctx.lineTo(x + 2, y + 8);
this.ctx.closePath();
this.ctx.fill();
// 绘制尾迹
this.ctx.beginPath();
const trailGradient = this.ctx.createLinearGradient(x, y+8, x, y+25);
trailGradient.addColorStop(0, 'rgba(255, 200, 100, 0.7)');
trailGradient.addColorStop(1, 'rgba(255, 100, 50, 0)');
this.ctx.fillStyle = trailGradient;
this.ctx.moveTo(x - 1, y + 8);
this.ctx.lineTo(x - 3, y + 25);
this.ctx.lineTo(x + 3, y + 25);
this.ctx.lineTo(x + 1, y + 8);
this.ctx.closePath();
this.ctx.fill();
}
clearCanvas() {
// 使用半透明的黑色矩形覆盖,产生拖尾效果
this.ctx.fillStyle = 'rgba(10, 10, 32, 0.1)';
this.ctx.fillRect(0, 0, this.width, this.height);
}
draw() {
this.clearCanvas();
for (const obj of this.particleData) {
if (obj.type === 'rocket') {
this.drawRocket(obj.x, obj.y);
} else if (obj.type === 'explosion') {
for (const p of obj.particles) {
const colorStr = this.applyColorMode(...p.color);
this.drawParticle(p.x, p.y, colorStr, p.alpha);
}
}
}
// 绘制一些静态的星星作为背景装饰
this.drawStars();
}
drawStars() {
// 只在初始化时生成一次星星位置
if (!this.stars) {
this.stars = [];
for (let i = 0; i < 150; i++) {
this.stars.push({
x: Math.random() * this.width,
y: Math.random() * this.height,
size: Math.random() * 1.5,
brightness: Math.random() * 0.5 + 0.5
});
}
}
this.ctx.fillStyle = 'white';
for (const star of this.stars) {
this.ctx.globalAlpha = star.brightness;
this.ctx.beginPath();
this.ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
this.ctx.fill();
}
this.ctx.globalAlpha = 1.0;
}
animate() {
const renderLoop = () => {
if (!this.isPaused) {
this.draw();
}
this.animationId = requestAnimationFrame(renderLoop);
};
renderLoop();
}
}
// 页面加载完成后初始化渲染器
window.addEventListener('DOMContentLoaded', () => {
const renderer = new FireworksRenderer('canvas');
});
现在,前端渲染引擎已经准备就绪。它定义了一个 FireworksRenderer 类,负责与后端通信、管理画布、应用不同的色彩模式,并以流畅的动画帧率绘制粒子。我们使用了Canvas的渐变和透明度来增强视觉效果,比如用径向渐变模拟粒子的光晕,用线性渐变绘制烟花的尾迹。
3. 搭建通信桥梁:轻量级Python服务器
为了让前端JavaScript能够获取到Python计算出的粒子数据,我们需要建立一个简单的HTTP服务器。Python的 http.server 模块结合 socketserver 和 threading 可以轻松实现一个异步数据接口。
创建一个 server.py 文件:
# server.py
import http.server
import socketserver
import json
import threading
import time
from fireworks_logic import FireworksManager
# 全局烟花管理器实例
fireworks_manager = FireworksManager(canvas_width=800, canvas_height=600)
def manager_loop():
"""在一个独立的线程中运行烟花模拟循环"""
while True:
fireworks_manager.update()
time.sleep(0.033) # 约30FPS的更新频率
# 启动后台模拟线程
simulation_thread = threading.Thread(target=manager_loop, daemon=True)
simulation_thread.start()
class FireworksRequestHandler(http.server.SimpleHTTPRequestHandler):
"""自定义请求处理器,提供数据接口"""
def do_GET(self):
# 处理获取当前帧数据的请求
if self.path == '/frame':
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*') # 允许跨域
self.end_headers()
# 获取当前所有烟花和粒子的数据
frame_data = fireworks_manager.get_all_render_data()
self.wfile.write(json.dumps(frame_data).encode('utf-8'))
# 默认行为:提供静态文件(如index.html)
else:
super().do_GET()
def do_POST(self):
# 处理手动发射烟花的请求
if self.path == '/launch':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
# 这里可以解析POST数据,但当前我们只需要触发发射
fireworks_manager.launch_firework()
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
response = {'status': 'success', 'message': 'Firework launched!'}
self.wfile.write(json.dumps(response).encode('utf-8'))
else:
self.send_error(404)
def log_message(self, format, *args):
# 可选:简化日志输出,避免控制台过于杂乱
pass
if __name__ == "__main__":
PORT = 8000
with socketserver.TCPServer(("", PORT), FireworksRequestHandler) as httpd:
print(f"烟花服务器已启动,访问 http://localhost:{PORT}")
print("按 Ctrl+C 停止服务器")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n服务器已关闭")
这个服务器做了三件事:
- 启动后台线程:持续运行烟花模拟逻辑,更新粒子状态。
- 提供GET接口
/frame:前端定期调用此接口,获取最新的粒子数据用于渲染。 - 提供POST接口
/launch:响应前端的“发射更多”按钮,手动添加一枚烟花。
现在,所有组件都已齐备。在项目根目录下打开终端,运行:
python server.py
然后在浏览器中访问 http://localhost:8000,你应该能看到一个深蓝色的夜空,烟花从底部升起、爆炸、消散,控制面板可以调整色彩模式和手动发射。一个由Python驱动、前端渲染的跨年烟花秀就完成了!
4. 高级定制与效果优化
基础效果跑通后,我们可以深入代码,进行各种个性化定制和性能优化,让你的烟花特效独一无二。
4.1 自定义烟花样式与行为
烟花的视觉效果由多个参数控制。我们可以修改 Firework 和 Particle 类的初始化参数来创造不同风格。
- 改变爆炸形状:默认爆炸是均匀的圆形。我们可以修改
Firework.explode()方法,让粒子沿特定图案发射。例如,实现一个心形爆炸:
# 在Firework类的explode方法中,替换粒子生成循环
def explode(self):
self.exploded = True
particle_count = 100
for i in range(particle_count):
# 心形曲线参数方程
t = i / particle_count * math.pi * 2
# 标准心形方程缩放
x = 16 * (math.sin(t) ** 3)
y = -(13 * math.cos(t) - 5 * math.cos(2*t) - 2 * math.cos(3*t) - math.cos(4*t))
# 调整缩放和速度
speed = random.uniform(1.5, 3)
vx = x * 0.05 * speed
vy = y * 0.05 * speed
color = (255, random.randint(50, 150), random.randint(100, 200)) # 粉紫色系
lifetime = random.randint(90, 150)
self.particles.append(Particle(self.x, self.y, vx, vy, color, lifetime))
- 调整物理参数:通过修改
Particle类中的gravity(重力)和drag(阻力),可以改变粒子的运动感觉。增大重力会让粒子更快下落,减小阻力会让粒子飞得更远。 - 颜色方案:在
Firework.__init__中,可以预设几种配色方案,并根据随机数选择。
# 在Firework类的__init__方法中,替换颜色生成逻辑
color_schemes = [
[(255, 50, 50), (255, 150, 50)], # 红-橙
[(50, 200, 255), (100, 100, 255)], # 蓝-紫
[(50, 255, 100), (200, 255, 50)], # 绿-黄绿
]
scheme = random.choice(color_schemes)
# 在爆炸时,粒子颜色可以在选定的配色方案中插值
4.2 性能优化技巧
当屏幕上同时存在成千上万个粒子时,性能可能成为瓶颈。这里有几个优化方向:
- 使用
requestAnimationFrame:前端渲染循环中我们已经使用了它,这是浏览器为动画优化的API,能确保绘制与屏幕刷新率同步,避免不必要的重绘。 - 粒子池(Object Pooling):频繁创建和销毁JavaScript或Python对象会产生垃圾回收开销。可以预先创建一定数量的粒子对象,循环使用。
// 在前端render.js中,可以添加一个简单的粒子池
class ParticlePool {
constructor(size) {
this.pool = new Array(size);
for(let i=0; i<size; i++) {
this.pool[i] = {x:0, y:0, color:'', alpha:0, active: false};
}
this.index = 0;
}
get() {
// 循环使用池中的对象
const obj = this.pool[this.index];
this.index = (this.index + 1) % this.pool.length;
obj.active = true;
return obj;
}
}
- 降低绘制调用:Canvas的
drawImage比绘制许多小形状更快。可以考虑将多个相同样式的小粒子预先绘制到一个离屏Canvas上,然后作为图像精灵批量绘制。 - 数据压缩:前后端传输的数据量会影响性能。如果粒子数量很多,可以考虑只传输变化量(delta)或使用二进制格式(如ArrayBuffer)而非JSON。
4.3 添加交互与音效
让用户能与特效互动,体验会大大提升。
- 鼠标交互:可以在前端监听鼠标点击事件,将点击坐标发送到后端,在点击位置生成一个烟花。
// 在render.js的initControls方法中添加
this.canvas.addEventListener('click', (event) => {
const rect = this.canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
fetch('/launch_at', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({x: x, y: y})
});
});
后端需要新增一个 /launch_at 接口来接收坐标并创建烟花。
- 添加音效:合适的音效能极大增强沉浸感。我们可以使用Web Audio API在烟花发射和爆炸时播放简短的音效。
// 在render.js中初始化音频上下文
class AudioManager {
constructor() {
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
this.gainNode = this.audioCtx.createGain();
this.gainNode.connect(this.audioCtx.destination);
this.gainNode.gain.value = 0.3; // 控制总体音量
}
playLaunchSound() {
// 播放一个简短的上升音调
const oscillator = this.audioCtx.createOscillator();
oscillator.connect(this.gainNode);
oscillator.frequency.setValueAtTime(600, this.audioCtx.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(200, this.audioCtx.currentTime + 0.8);
oscillator.start();
oscillator.stop(this.audioCtx.currentTime + 0.8);
}
playExplosionSound() {
// 播放一个爆炸的白噪声
// ... 白噪声生成代码
}
}
// 在获取到新数据,发现有新火箭或爆炸时触发相应音效
4.4 响应式设计与移动端适配
我们的特效需要在不同设备上都有良好表现。
- Canvas分辨率适配:在
resizeCanvas方法中,我们根据容器大小设置Canvas的width和height属性(而不是CSS样式)。这确保了在高DPI屏幕上也能清晰渲染。 - 触摸事件支持:为移动设备添加触摸事件监听,实现触摸发射烟花。
this.canvas.addEventListener('touchstart', (event) => {
event.preventDefault(); // 阻止默认滚动行为
const touch = event.touches[0];
const rect = this.canvas.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
// 发送触摸坐标到后端
fetch('/launch_at', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({x: x, y: y})
});
}, { passive: false });
- 性能降级策略:在性能较弱的移动设备上,可以动态减少同时存在的烟花数量、降低粒子数量或减少渲染频率。
// 根据帧率动态调整
let frameCount = 0;
let lastTime = performance.now();
let fps = 60;
const checkPerformance = (currentTime) => {
frameCount++;
if (currentTime - lastTime >= 1000) {
fps = frameCount;
frameCount = 0;
lastTime = currentTime;
// 如果帧率过低,降低特效复杂度
if (fps < 30) {
this.simplifyEffects();
}
}
};
通过以上这些定制和优化,你的烟花特效将从基础演示升级为一个健壮、美观、交互性强的完整项目。你可以根据具体的使用场景(如个人网站背景、活动页面、产品演示)来调整参数和功能。
更多推荐


所有评论(0)