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

JavaScript油漆桶工具实现遇RangeError:调用栈溢出问题求助

解决泛洪填充递归栈溢出问题

你遇到的Uncaught RangeError: Maximum call stack size exceeded错误,是因为递归版泛洪填充在处理大面积区域时,函数调用层级太深,超出了JavaScript的调用栈上限。递归实现虽简洁,但不适合大区域填充,改成**迭代版(基于栈/队列)**就能解决这个问题。

另外你的代码还有几个可优化点:

  • 每次修改单个像素就调用putImageData会严重拖慢性能,应该一次性修改完所有像素后再更新画布
  • 缺少边界检查,可能访问数组越界的位置
  • 未标记已填充像素,可能导致重复处理

以下是修复并优化后的完整代码:

<canvas class="canvas"></canvas>
body {
    background-color: #000;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}
const canvas = document.querySelector('.canvas')
const ctx = canvas.getContext('2d', {
    willReadFrequently: true
})
canvas.width = 400
canvas.height = 400

ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)

ctx.strokeStyle = '#000'
ctx.lineWidth = 2
ctx.strokeRect(8, 8, canvas.width - 16, canvas.height - 16)

// 批量设置单个像素颜色,最后统一更新画布
const setPixelColor = (imageData, pixelPos, newColor) => {
    imageData.data[pixelPos] = newColor.r
    imageData.data[pixelPos + 1] = newColor.g
    imageData.data[pixelPos + 2] = newColor.b
    imageData.data[pixelPos + 3] = newColor.a
}

// 迭代版泛洪填充(基于栈实现)
const floodFill = (startPixelPos, imageData, oldColor, newColor) => {
    const pixelStack = [startPixelPos]
    const pixelCount = imageData.data.length

    // 提前判断新旧颜色是否一致,避免无效填充
    if (oldColor.r === newColor.r && oldColor.g === newColor.g && oldColor.b === newColor.b && oldColor.a === newColor.a) {
        return
    }

    while (pixelStack.length > 0) {
        const pixelPos = pixelStack.pop()

        // 边界检查:确保像素位置在有效范围内
        if (pixelPos < 0 || pixelPos >= pixelCount || pixelPos % 4 !== 0) {
            continue
        }

        // 检查当前像素是否为目标旧颜色
        if (
            imageData.data[pixelPos] === oldColor.r &&
            imageData.data[pixelPos + 1] === oldColor.g &&
            imageData.data[pixelPos + 2] === oldColor.b &&
            imageData.data[pixelPos + 3] === oldColor.a
        ) {
            // 设置新颜色
            setPixelColor(imageData, pixelPos, newColor)

            // 将上下左右的像素加入栈等待处理
            const top = pixelPos - canvas.width * 4
            const bottom = pixelPos + canvas.width * 4
            const left = pixelPos - 4
            const right = pixelPos + 4

            pixelStack.push(top)
            pixelStack.push(bottom)
            pixelStack.push(left)
            pixelStack.push(right)
        }
    }

    // 所有像素处理完成后,一次性更新画布
    ctx.putImageData(imageData, 0, 0)
}

addEventListener('mousedown', e => {
    const rect = canvas.getBoundingClientRect(),
        x = Math.floor(e.x - rect.x),
        y = Math.floor(e.y - rect.y)

    if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) return

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
    const pixelPos = (y * canvas.width + x) * 4
    const oldColor = {
        r: imageData.data[pixelPos],
        g: imageData.data[pixelPos + 1],
        b: imageData.data[pixelPos + 2],
        a: imageData.data[pixelPos + 3],
    }
    const newColor = {
        r: 0,
        g: 255,
        b: 0,
        a: 255,
    }

    floodFill(pixelPos, imageData, oldColor, newColor)
})

关键改进说明:

  1. 迭代替代递归:用栈pixelStack存储待处理像素位置,通过循环处理栈内元素,彻底避免递归调用栈溢出问题。
  2. 边界检查:每次取出像素位置时先判断是否在有效范围内,防止访问数组越界导致的异常。
  3. 批量更新画布:仅在所有像素处理完成后调用一次putImageData,大幅提升填充效率。
  4. 提前终止判断:如果新旧颜色完全一致,直接返回操作,避免无意义的计算。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:25:15