You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Canvas的SeeMusic钢琴下落动画新生成瓦片速度异常问题

问题根因

瓦片速度逐次升高的问题来自两个核心bug:

  1. Update渲染循环被重复触发
    你在SpawnTile函数里每次生成新瓦片都会调用requestAnimationFrame(Update),而Update函数本身也在递归调用requestAnimationFrame(Update)。这意味着每生成一个瓦片,渲染循环的触发次数就会翻倍,相当于每多一个瓦片全局帧率就翻一倍,所有瓦片的运动速度自然会越来越快。
  2. mouseup事件重复绑定
    你在Tile类的Animate方法里每帧都给window新增一个mouseup事件监听器,随着时间推移,单次mouseup触发会执行成百上千个回调,不仅导致性能卡顿,还会让所有历史瓦片的运动参数被重复修改。

修复代码

JS部分修改如下

const canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
keys = document.querySelectorAll(".key")
document.querySelector(".keyboard").style.gridTemplateColumns = "repeat("+keys.length+", 1fr)"
canvas.width = window.innerWidth
canvas.height = window.innerHeight*0.8

// Variables
let tile,
particle,
tiles = [],
tileX,
tileWidth = keys[0].offsetWidth

class Tile {
    constructor (x, y, h, dh, dy, c) {
        this.x = x
        this.y = y
        this.h = h
        this.c = c
        this.dh = dh
        this.dy = dy
        // 只在瓦片创建时绑定一次mouseup事件,触发后自动解绑避免内存泄漏
        this.stopGrowing = () => {
            this.dh = 0
            this.dy = 2
            window.removeEventListener('mouseup', this.stopGrowing)
        }
        window.addEventListener('mouseup', this.stopGrowing)
    }

    Animate() {
        this.h += this.dh
        this.y += this.dy
        this.Draw()
    }

    Draw() {
        ctx.beginPath()
        ctx.fillStyle = this.c
        ctx.fillRect(this.x, this.y, keys[0].offsetWidth, this.h)
        ctx.closePath()
    }
}

function SpawnTile() {
    tiles.push(new Tile(tileX, 0, 0, 2, 0, "red"))
}

// 全局仅启动一次渲染循环即可
requestAnimationFrame(Update)
function Update() {
    requestAnimationFrame(Update)
    ctx.clearRect(0,0,canvas.width, canvas.height)
    tiles.forEach(tile => {
        tile.Animate()
    })
}

keys.forEach(key => {
    key.addEventListener("mousedown", () => {
        tileX = key.getBoundingClientRect().left
        SpawnTile()
    })
})

额外优化建议

可以添加瓦片超出画布边界后的自动销毁逻辑,避免tiles数组无限增长占用内存:

// 替换原来的tiles.forEach逻辑
tiles = tiles.filter(tile => {
  tile.Animate()
  // 瓦片完全移出画布后就从数组中移除
  return tile.y < canvas.height
})

内容的提问来源于stack exchange,提问作者Amadeus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 06:57:00