欢迎光临
我们一直在努力

基于Canvas和Web Audio API的交互式烟花动画网页游戏

一个基于 Canvas 和 Web Audio API 的交互式烟花动画网页


目录

  • 整体架构
  • HTML 结构
  • CSS 样式
  • JavaScript 核心模块
  • 用户交互
  • 性能优化
  • iOS 适配
  • 文件依赖

  • 一、整体架构

    ┌─────────────────────────────────────────────────────────────┐
    │ HTML 结构 │
    ├─────────────────────────────────────────────────────────────┤
    │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
    │ │ SVG 图标 │ │ Canvas容器 │ │ 控制面板/菜单 │ │
    │ └─────────────┘ └─────────────┘ └─────────────────────┘ │
    ├─────────────────────────────────────────────────────────────┤
    │ CSS 样式 │
    ├─────────────────────────────────────────────────────────────┤
    │ JavaScript 逻辑 │
    │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
    │ │ FireworkSound│ │ Store │ │ Shell/Star/Spark │ │
    │ │ 音效模块 │ │ 状态管理 │ │ 粒子系统 │ │
    │ └──────────────┘ └──────────────┘ └──────────────────────┘ │
    └─────────────────────────────────────────────────────────────┘


    二、HTML 结构部分

    1. 头部元信息

    <meta charset="UTF-8">
    <title>2026新年快乐!万事如意!</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">

    说明:

    • 设置字符编码为 UTF-8
    • 页面标题显示新年祝福
    • viewport-fit=cover 适配 iPhone X 系列刘海屏
    • 禁用用户缩放,防止误操作

    2. iOS PWA 支持

    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="apple-mobile-web-app-title" content="烟花盛宴">
    <link rel="apple-touch-icon" href="">
    <link rel="manifest" href="data:application/json;base64,…">

    说明:

    • 支持添加到 iOS 主屏幕
    • 半透明状态栏适配刘海屏
    • 内联 PWA manifest(Base64 编码)

    3. SVG 图标定义

    <svg xmlns="http://www.w3.org/2000/svg">
    <symbol id="icon-play" viewBox="0 0 24 24">
    <path d="M8 5v14l11-7z"/>
    </symbol>
    <symbol id="icon-pause" viewBox="0 0 24 24">
    <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
    </symbol>
    <symbol id="icon-close" viewBox="0 0 24 24"></symbol>
    <symbol id="icon-settings" viewBox="0 0 24 24"></symbol>
    <symbol id="icon-shutter-fast" viewBox="0 0 24 24"></symbol>
    <symbol id="icon-shutter-slow" viewBox="0 0 24 24"></symbol>
    </svg>

    说明:

    • 使用 SVG symbol 定义可复用的图标
    • 通过 <use href="#icon-xxx"> 引用
    • 优点:矢量图标、可缩放、体积小

    4. Canvas 画布容器

    <div id="canvas-container">
    <canvas id="trails-canvas"></canvas> <!– 拖尾轨迹层 –>
    <canvas id="main-canvas"></canvas> <!– 主画面层 –>
    </div>

    双层 Canvas 设计原理:

    层级作用渲染方式
    trails-canvas 绘制烟花拖尾效果 渐变透明覆盖实现残影
    main-canvas 绘制当前帧的粒子和UI元素 每帧清空重绘

    5. 控制面板

    <div id="controls">
    <div id="pause-btn" class="btn">
    <svg fill="white" width="24" height="24"><use href="#icon-pause"></use></svg>
    </div>
    <div id="shutter-btn" class="btn">
    <svg fill="white" width="24" height="24"><use href="#icon-shutter-slow"></use></svg>
    </div>
    <div id="settings-btn" class="btn">
    <svg fill="white" width="24" height="24"><use href="#icon-settings"></use></svg>
    </div>
    </div>

    按钮功能:

    • 暂停/播放:控制动画运行
    • 快门模式:切换长曝光效果
    • 设置:打开设置菜单

    6. 设置菜单

    <div id="menu">
    <div id="menu__header">设置</div>
    <form>
    <div class="form-option form-option–select">
    <label>烟花类型</label>
    <select id="shell-type"></select>
    </div>
    <div class="form-option form-option–select">
    <label>烟花大小</label>
    <select id="shell-size"></select>
    </div>
    <div class="form-option form-option–checkbox">
    <label><input id="auto-launch" type="checkbox" /><span>自动发射</span></label>
    </div>
    <div class="form-option form-option–checkbox">
    <label><input id="finale-mode" type="checkbox" /><span>终场模式</span></label>
    </div>
    <div class="form-option form-option–checkbox">
    <label><input id="hide-controls" type="checkbox" /><span>隐藏控制</span></label>
    </div>
    <div class="form-option form-option–checkbox">
    <label><input id="sound-enabled" type="checkbox" checked /><span>声音效果</span></label>
    </div>
    <div class="form-option form-option–select">
    <label>声音类型</label>
    <select id="sound-type"></select>
    </div>
    </form>
    </div>

    7. 首次交互提示(iOS 适配)

    <div id="audio-prompt">
    <div id="audio-prompt__icon">🎆</div>
    <div id="audio-prompt__text">点击屏幕开始烟花盛宴</div>
    <div id="audio-prompt__hint">Tap to start fireworks</div>
    </div>

    说明:

    • iOS Safari 要求用户交互后才能播放音频
    • 此提示引导用户点击以解锁音频

    三、CSS 样式部分

    1. 全局样式与触摸优化

    * {
    position: relative;
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent; /* 移除点击高亮 */
    -webkit-touch-callout: none; /* 禁用长按菜单 */
    -webkit-user-select: none; /* 禁止选择文字 */
    user-select: none;
    touch-action: manipulation; /* 禁用双击缩放 */
    }

    html { background-color: #000; }

    body {
    overflow: hidden;
    color: rgba(255, 255, 255, 0.5);
    font-family: "Russo One", arial, sans-serif;
    overscroll-behavior: none; /* 禁用橡皮筋效果 */
    padding: env(safe-area-inset-top) env(safe-area-inset-right)
    env(safe-area-inset-bottom) env(safe-area-inset-left); /* 安全区域 */
    }

    2. 混合模式

    #canvas-container canvas {
    position: absolute;
    mix-blend-mode: lighten; /* 变亮混合模式 */
    }

    说明:

    • lighten 混合模式使亮色粒子在黑色背景上更加突出
    • 多个亮色叠加会产生更亮的效果

    3. 动画过渡

    .hide {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s, visibility 0.3s;
    }

    说明:

    • 使用 opacity + visibility 组合实现平滑的显示/隐藏动画
    • visibility 延迟隐藏,避免点击穿透

    4. 音频提示动画

    #audio-prompt__icon {
    font-size: 80px;
    animation: pulse 1.5s ease-in-out infinite;
    }

    @keyframes pulse {
    0%, 100% { transform: scale(1); opacity: 1; }
    50% { transform: scale(1.1); opacity: 0.7; }
    }


    四、JavaScript 核心模块

    1. FireworkSound 音效模块

    const FireworkSound = {
    ctx: null, // AudioContext 实例
    enabled: true, // 是否启用声音
    soundType: 'realistic', // 当前声音类型
    isUnlocked: false, // iOS 音频解锁状态

    init() {
    // 初始化 Web Audio API
    this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    },

    unlock() {
    // iOS 音频解锁:播放静音缓冲区
    if (this.isUnlocked) return true;
    if (this.ctx.state === 'suspended') {
    this.ctx.resume();
    }
    const buffer = this.ctx.createBuffer(1, 1, 22050);
    const source = this.ctx.createBufferSource();
    source.buffer = buffer;
    source.connect(this.ctx.destination);
    source.start(0);
    this.isUnlocked = true;
    },

    playLaunch() { /* 发射音效 */ },
    playBurst(size) { /* 爆炸音效 */ }
    };

    Web Audio API 核心概念:

    组件作用示例用途
    AudioContext 音频上下文,管理所有音频节点 new AudioContext()
    OscillatorNode 振荡器,生成周期性波形 正弦波、方波、锯齿波
    GainNode 增益节点,控制音量 音量包络、淡入淡出
    BiquadFilter 双二阶滤波器 低通、高通、带通滤波
    BufferSource 缓冲源,播放采样数据 白噪声、爆炸声

    真实爆炸声实现原理:

    playRealisticBurst(size = 1) {
    // 1. 生成白噪声缓冲区
    const duration = 0.6 + size * 0.2;
    const bufferSize = this.ctx.sampleRate * duration;
    const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
    const data = buffer.getChannelData(0);

    for (let i = 0; i < bufferSize; i++) {
    const t = i / bufferSize;
    const noise = Math.random() * 2 1; // 随机噪声 [-1, 1]
    const envelope = Math.pow(1 t, 1.5); // 指数衰减包络
    data[i] = noise * envelope;
    }

    // 2. 创建低通滤波器模拟爆炸闷响
    const filter = this.ctx.createBiquadFilter();
    filter.type = 'lowpass';
    filter.frequency.setValueAtTime(3000 * size, this.ctx.currentTime);
    filter.frequency.exponentialRampToValueAtTime(200, this.ctx.currentTime + duration * 0.7);

    // 3. 音量包络:快速起音,缓慢衰减
    const gain = this.ctx.createGain();
    gain.gain.setValueAtTime(0, this.ctx.currentTime);
    gain.gain.linearRampToValueAtTime(volume, this.ctx.currentTime + 0.02); // 起音 20ms
    gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration); // 衰减

    // 4. 连接节点并播放
    noise.connect(filter);
    filter.connect(gain);
    gain.connect(this.ctx.destination);
    noise.start(this.ctx.currentTime);
    }

    声音类型对照表:

    类型中文名特点
    realistic 真实 白噪声 + 低频冲击 + 噼啪声
    cinematic 电影感 深沉隆隆声 + 闪烁火花
    classic 经典烟花 清脆爆破声
    retro 复古8位 方波游戏机风格
    soft 柔和轻柔 正弦波轻柔音效
    synth 电子合成 多振荡器合成器
    drum 鼓点贝斯 底鼓 + 军鼓 + 踩镲
    whistle 哨子爆破 口哨式发射 + 爆破

    2. Store 状态管理

    const store = {
    _listeners: new Set(), // 订阅者集合

    state: {
    paused: false,
    longExposure: false,
    menuOpen: false,
    config: {
    shell: 'Random',
    size: '3',
    autoLaunch: true,
    finale: false,
    hideControls: false,
    soundEnabled: true,
    soundType: 'realistic'
    }
    },

    setState(nextState) {
    this.state = Object.assign({}, this.state, nextState);
    this._dispatch(); // 通知所有订阅者
    this.persist(); // 持久化到 localStorage
    },

    subscribe(listener) {
    this._listeners.add(listener);
    return () => this._listeners.delete(listener);
    },

    load() {
    // 从 localStorage 加载配置
    },

    persist() {
    // 保存配置到 localStorage
    }
    };

    发布-订阅模式流程:

    用户操作 → updateConfig() → store.setState() → _dispatch() → renderApp()

    更新 DOM

    3. Shell 烟花类

    class Shell {
    constructor(options) {
    this.size = options.size; // 爆炸半径
    this.starCount = options.starCount; // 星星数量
    this.starLife = options.starLife; // 星星寿命(毫秒)
    this.color = options.color; // 颜色
    this.glitter = options.glitter; // 闪光类型
    this.pistil = options.pistil; // 是否有花心
    this.ring = options.ring; // 是否环形
    }

    launch(position, launchHeight) {
    // 1. 计算发射位置和目标高度
    const launchX = position * (width hpad * 2) + hpad;
    const launchY = height;
    const burstY = minHeight (launchHeight * (minHeight vpad));
    const launchDistance = launchY burstY;

    // 2. 计算发射速度(抛物线运动近似)
    const launchVelocity = Math.pow(launchDistance * 0.04, 0.64);

    // 3. 创建彗星(上升的火球)
    const comet = Star.add(
    launchX, launchY,
    this.color || COLOR.White,
    Math.PI, // 向上发射
    launchVelocity,
    launchVelocity * 400 // 寿命
    );
    comet.heavy = true; // 减少空气阻力
    comet.sparkFreq = 16; // 火花频率

    // 4. 设置死亡回调为爆炸
    comet.onDeath = () => this.burst(comet.x, comet.y);

    // 5. 播放发射音效
    FireworkSound.playLaunch();
    }

    burst(x, y) {
    // 1. 计算爆炸速度
    const speed = this.size / 96;

    // 2. 创建粒子弧(圆形分布)
    createParticleArc(0, PI_2, this.starCount, 1, (angle) => {
    const star = Star.add(
    x, y,
    this.color,
    angle,
    Math.pow(Math.random(), 0.45) * speed, // 非线性速度分布
    this.starLife + Math.random() * this.starLife * this.starLifeVariation
    );
    star.onDeath = onDeath; // 设置死亡效果
    });

    // 3. 播放爆炸音效
    const soundSize = Math.min(2, this.size / 200);
    FireworkSound.playBurst(soundSize);
    }
    }

    烟花类型对照表:

    类型中文名特点视觉效果
    Crysanthemum 菊花 经典球形爆炸,密集粒子 大型圆形散开
    Palm 棕榈 垂直拖尾,像棕榈树 向下垂落
    Ring 圆环 环形爆炸 空心圆环
    Crossette 十字 爆炸后分裂成四个小星星 二次爆炸
    Floral 花簇 小型二次爆炸 多层绽放
    Crackle 噼啪 金色火花,噼啪声 金色闪烁
    Willow 垂柳 长时间下坠的火花 长尾下垂
    Falling Leaves 落叶 飘落的金色粒子 缓慢飘落
    Horse Tail 马尾 水平拖尾 横向展开

    4. Star 星星粒子

    const Star = {
    drawWidth: 3,
    airDrag: 0.98,
    airDragHeavy: 0.992,

    active: createParticleCollection(), // 按颜色分组的活跃粒子
    _pool: [], // 对象池,复用粒子对象

    add(x, y, color, angle, speed, life, speedOffX, speedOffY) {
    // 从池中获取或创建新对象
    const instance = this._pool.pop() || {};

    instance.x = x;
    instance.y = y;
    instance.prevX = x; // 上一帧位置(用于绘制轨迹)
    instance.prevY = y;
    instance.color = color;
    instance.speedX = Math.sin(angle) * speed + (speedOffX || 0);
    instance.speedY = Math.cos(angle) * speed + (speedOffY || 0);
    instance.life = life;
    instance.sparkFreq = 0; // 火花频率

    this.active[color].push(instance);
    return instance;
    },

    returnInstance(instance) {
    instance.onDeath && instance.onDeath(instance);
    instance.onDeath = null;
    this._pool.push(instance); // 回收到池中
    }
    };

    对象池模式优势:

    • 避免频繁创建/销毁对象
    • 减少垃圾回收(GC)压力
    • 提高大量粒子时的性能

    5. Spark 火花粒子

    const Spark = {
    drawWidth: 0.75,
    airDrag: 0.9,

    active: createParticleCollection(),
    _pool: [],

    add(x, y, color, angle, speed, life) {
    const instance = this._pool.pop() || {};
    instance.x = x;
    instance.y = y;
    instance.prevX = x;
    instance.prevY = y;
    instance.color = color;
    instance.speedX = Math.sin(angle) * speed;
    instance.speedY = Math.cos(angle) * speed;
    instance.life = life;
    this.active[color].push(instance);
    return instance;
    }
    };

    Star 与 Spark 的区别:

    特性StarSpark
    大小 3px 0.75px
    空气阻力 0.98 0.9
    用途 主要烟花粒子 闪光效果
    寿命 较长 较短

    6. 渲染循环

    function update(frameTime, lag) {
    if (!canInteract()) return;

    const timeStep = frameTime * simSpeed;
    const speed = simSpeed * lag;

    // 1. 更新全局状态(自动发射等)
    updateGlobals(timeStep, lag);

    // 2. 计算物理参数
    const starDrag = 1 (1 Star.airDrag) * speed; // 空气阻力系数
    const sparkDrag = 1 (1 Spark.airDrag) * speed;
    const gAcc = timeStep / 1000 * GRAVITY; // 重力加速度

    // 3. 更新所有粒子
    COLOR_CODES_W_INVIS.forEach(color => {
    // 更新星星
    Star.active[color].forEach((star, i, stars) => {
    star.life -= timeStep;
    if (star.life <= 0) {
    stars.splice(i, 1);
    Star.returnInstance(star);
    } else {
    // 保存上一帧位置
    star.prevX = star.x;
    star.prevY = star.y;

    // 物理更新
    star.x += star.speedX * speed;
    star.y += star.speedY * speed;
    star.speedX *= starDrag;
    star.speedY *= starDrag;
    star.speedY += gAcc; // 重力

    // 旋转效果
    if (star.spinRadius) {
    star.spinAngle += star.spinSpeed * speed;
    star.x += Math.sin(star.spinAngle) * star.spinRadius * speed;
    star.y += Math.cos(star.spinAngle) * star.spinRadius * speed;
    }

    // 火花效果
    if (star.sparkFreq) {
    star.sparkTimer -= timeStep;
    while (star.sparkTimer < 0) {
    star.sparkTimer += star.sparkFreq;
    Spark.add(star.x, star.y, star.sparkColor, );
    }
    }
    }
    });

    // 更新火花
    Spark.active[color].forEach((spark, i, sparks) => {
    // 类似星星的物理更新…
    });
    });

    // 4. 渲染
    render(speed);
    }

    7. 渲染函数

    function render(speed) {
    const { dpr, width, height } = mainStage;
    const trailsCtx = trailsStage.ctx;
    const mainCtx = mainStage.ctx;

    // 1. 更新天空颜色
    colorSky(speed);

    // 2. 设置变换
    trailsCtx.scale(dpr, dpr);
    mainCtx.scale(dpr, dpr);

    // 3. 绘制拖尾效果(渐变黑色覆盖)
    trailsCtx.globalCompositeOperation = 'source-over';
    trailsCtx.fillStyle = `rgba(0, 0, 0, ${longExposure ? 0.0025 : 0.1 * speed})`;
    trailsCtx.fillRect(0, 0, width, height);

    // 4. 设置混合模式
    trailsCtx.globalCompositeOperation = 'lighten';

    // 5. 清空主画布
    mainCtx.clearRect(0, 0, width, height);

    // 6. 绘制爆炸闪光
    while (BurstFlash.active.length) {
    const bf = BurstFlash.active.pop();
    const gradient = trailsCtx.createRadialGradient(bf.x, bf.y, 0, bf.x, bf.y, bf.radius);
    gradient.addColorStop(0.05, 'white');
    gradient.addColorStop(0.25, 'rgba(255, 160, 20, 0.2)');
    gradient.addColorStop(1, 'rgba(255, 160, 20, 0)');
    trailsCtx.fillStyle = gradient;
    trailsCtx.fillRect(bf.x bf.radius, bf.y bf.radius, bf.radius * 2, bf.radius * 2);
    }

    // 7. 绘制星星轨迹
    trailsCtx.lineWidth = Star.drawWidth;
    trailsCtx.lineCap = 'round';
    COLOR_CODES.forEach(color => {
    const stars = Star.active[color];
    trailsCtx.strokeStyle = color;
    trailsCtx.beginPath();
    stars.forEach(star => {
    trailsCtx.moveTo(star.x, star.y);
    trailsCtx.lineTo(star.prevX, star.prevY);
    });
    trailsCtx.stroke();
    });

    // 8. 绘制火花
    trailsCtx.lineWidth = Spark.drawWidth;
    trailsCtx.lineCap = 'butt';
    // 类似星星的绘制…

    // 9. 绘制速度条
    if (speedBarOpacity) {
    mainCtx.globalAlpha = speedBarOpacity;
    mainCtx.fillStyle = COLOR.Blue;
    mainCtx.fillRect(0, height 6, width * simSpeed, 6);
    mainCtx.globalAlpha = 1;
    }

    // 10. 重置变换
    trailsCtx.resetTransform();
    mainCtx.resetTransform();
    }

    8. 天空颜色计算

    const currentSkyColor = { r: 0, g: 0, b: 0 };
    const targetSkyColor = { r: 0, g: 0, b: 0 };

    function colorSky(speed) {
    const maxSkySaturation = 30; // 最大天空饱和度
    const maxStarCount = 500; // 达到最大亮度所需的星星数量

    let totalStarCount = 0;
    targetSkyColor.r = 0;
    targetSkyColor.g = 0;
    targetSkyColor.b = 0;

    // 累计所有颜色的亮度
    COLOR_CODES.forEach(color => {
    const tuple = COLOR_TUPLES[color];
    const count = Star.active[color].length;
    totalStarCount += count;
    targetSkyColor.r += tuple.r * count;
    targetSkyColor.g += tuple.g * count;
    targetSkyColor.b += tuple.b * count;
    });

    // 非线性映射,模拟人眼感知
    const intensity = Math.pow(Math.min(1, totalStarCount / maxStarCount), 0.3);

    // 归一化并应用强度
    const maxColorComponent = Math.max(1, targetSkyColor.r, targetSkyColor.g, targetSkyColor.b);
    targetSkyColor.r = targetSkyColor.r / maxColorComponent * maxSkySaturation * intensity;
    targetSkyColor.g = targetSkyColor.g / maxColorComponent * maxSkySaturation * intensity;
    targetSkyColor.b = targetSkyColor.b / maxColorComponent * maxSkySaturation * intensity;

    // 平滑过渡
    const colorChange = 10;
    currentSkyColor.r += (targetSkyColor.r currentSkyColor.r) / colorChange * speed;
    currentSkyColor.g += (targetSkyColor.g currentSkyColor.g) / colorChange * speed;
    currentSkyColor.b += (targetSkyColor.b currentSkyColor.b) / colorChange * speed;

    // 应用到背景
    appNodes.canvasContainer.style.backgroundColor =
    `rgb(${currentSkyColor.r | 0}, ${currentSkyColor.g | 0}, ${currentSkyColor.b | 0})`;
    }


    五、用户交互

    1. 点击发射

    function handlePointerStart(event) {
    activePointerCount++;
    FireworkSound.unlock(); // iOS 音频解锁
    FireworkSound.resume();

    const btnSize = 44;

    // 点击顶部按钮区域
    if (event.y < btnSize) {
    if (event.x < btnSize) {
    togglePause();
    return;
    }
    if (event.x > mainStage.width/2 btnSize/2 &&
    event.x < mainStage.width/2 + btnSize/2) {
    toggleLongExposure();
    return;
    }
    if (event.x > mainStage.width btnSize) {
    toggleMenu();
    return;
    }
    }

    // 点击画布发射烟花
    if (!canInteract()) return;
    if (updateSpeedFromEvent(event)) {
    isUpdatingSpeed = true;
    } else if (event.onCanvas) {
    launchShellFromConfig(event);
    }
    }

    2. 键盘快捷键

    function handleKeydown(event) {
    if (event.keyCode === 80) togglePause(); // P 键 – 暂停
    if (event.keyCode === 79) toggleMenu(); // O 键 – 菜单
    if (event.keyCode === 27) toggleMenu(false); // ESC 键 – 关闭菜单
    }

    3. 自动发射序列

    function startSequence() {
    if (isFirstSeq) {
    isFirstSeq = false;
    const shell = new Shell(crysanthemumShell(shellSizeSelector()));
    shell.launch(0.5, 0.5); // 首发居中
    return 2400;
    }

    if (finaleSelector()) {
    // 终场模式:快速连续发射
    seqRandomShell();
    if (currentFinaleCount < finaleCount) {
    currentFinaleCount++;
    return 170; // 170ms 间隔
    } else {
    currentFinaleCount = 0;
    return 6000; // 暂停 6 秒
    }
    }

    // 随机选择发射模式
    const rand = Math.random();
    if (rand < 0.2) return seqSmallBarrage(); // 小型连发
    if (rand < 0.6) return seqRandomShell(); // 单发
    if (rand < 0.8) return seqTwoRandom(); // 双发
    return seqTriple(); // 三发
    }


    六、性能优化要点

    优化技术说明效果
    对象池 复用 Star/Spark 对象 减少 GC 压力
    颜色分组 按颜色分组批量渲染 减少 Canvas 状态切换
    双缓冲 trails-canvas 保留历史轨迹 实现拖尾效果
    requestAnimationFrame 与屏幕刷新同步 流畅动画
    空气阻力模拟 粒子速度衰减 自然消散效果
    设备像素比适配 scale(dpr, dpr) 高清显示

    七、iOS 适配

    1. Meta 标签优化

    <!– 视口适配刘海屏 –>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">

    <!– iOS Web App 支持 –>
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="apple-mobile-web-app-title" content="烟花盛宴">

    <!– 禁用电话号码识别 –>
    <meta name="format-detection" content="telephone=no">

    <!– PWA Manifest –>
    <link rel="manifest" href="data:application/json;base64,…">

    2. CSS 触摸优化

    * {
    -webkit-tap-highlight-color: transparent; /* 移除点击高亮 */
    -webkit-touch-callout: none; /* 禁用长按菜单 */
    -webkit-user-select: none; /* 禁止选择文字 */
    touch-action: manipulation; /* 禁用双击缩放 */
    }

    body {
    overscroll-behavior: none; /* 禁用橡皮筋效果 */
    padding: env(safe-area-inset-*); /* 适配安全区域 */
    }

    3. Web Audio 解锁

    unlock() {
    if (this.isUnlocked) return true;

    // iOS 要求:必须在用户交互中创建并播放音频
    if (this.ctx.state === 'suspended') {
    this.ctx.resume();
    }

    // 播放静音缓冲区解锁音频
    const buffer = this.ctx.createBuffer(1, 1, 22050);
    const source = this.ctx.createBufferSource();
    source.buffer = buffer;
    source.connect(this.ctx.destination);
    source.start(0);

    this.isUnlocked = true;
    }

    4. 手势防护

    // 禁用 iOS 缩放手势
    document.addEventListener('gesturestart', (e) => e.preventDefault());
    document.addEventListener('gesturechange', (e) => e.preventDefault());
    document.addEventListener('gestureend', (e) => e.preventDefault());

    // 防止双击缩放
    let lastTouchEnd = 0;
    document.addEventListener('touchend', (e) => {
    const now = Date.now();
    if (now lastTouchEnd <= 300) {
    e.preventDefault();
    }
    lastTouchEnd = now;
    });

    5. iOS 支持功能表

    功能状态说明
    添加到主屏幕 支持全屏运行
    声音播放 用户交互后解锁
    刘海屏适配 安全区域 padding
    禁用缩放 手势防护
    禁用橡皮筋效果 overscroll-behavior
    触摸响应 Pointer Events

    八、文件依赖

    外部库

    ├── fscreen@1.0.1.js – 全屏 API 兼容库
    ├── Stage@0.1.4.js – Canvas 舞台管理
    └── MyMath.js – 数学工具函数

    本地文件

    └── style.css – 额外样式(可选)


    总结

    这是一个功能完整的交互式烟花动画网页,主要特点:

  • 视觉效果:双层 Canvas 实现拖尾效果,混合模式增强亮度
  • 音效系统:8 种不同风格的音效,Web Audio API 实时生成
  • 物理模拟:重力、空气阻力、粒子生命周期
  • 状态管理:发布-订阅模式,localStorage 持久化
  • 性能优化:对象池、颜色分组、requestAnimationFrame
  • 跨平台:完整的 iOS/移动端适配

  • 赞(0)
    未经允许不得转载:171主机测评 » 基于Canvas和Web Audio API的交互式烟花动画网页游戏
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址