如何为像素网格实现JavaScript版FloodFill函数?
像素网格绘图工具的FloodFill(油漆桶)实现方案
核心问题分析
你之前尝试DFS/BFS失败,大概率是颜色判断逻辑不严谨导致的:浏览器返回的元素颜色格式不统一(比如rgb(0,0,0)、#000、black都是黑色,但字符串完全不同),或者没有处理边界/重复遍历的问题。下面是可直接复用的实现方案及原理。
实现代码
1. 颜色标准化工具函数
先统一颜色格式为十六进制,解决不同格式颜色无法匹配的问题:
function normalizeColor(color) { // 创建临时元素获取计算后的标准颜色 const tempEl = document.createElement('div'); tempEl.style.color = color; document.body.appendChild(tempEl); const computedColor = getComputedStyle(tempEl).color; document.body.removeChild(tempEl); // 将RGB/RGBA转为十六进制 if (computedColor.startsWith('rgb')) { const values = computedColor.match(/\d+/g).map(Number); return '#' + values.slice(0,3).map(v => v.toString(16).padStart(2, '0')).join(''); } return computedColor.toLowerCase(); }
2. BFS版FloodFill核心函数
推荐用BFS(队列实现),避免DFS递归导致的栈溢出问题:
function floodFill(startPixel, targetColor) { const originalColor = normalizeColor(startPixel.style.backgroundColor); const normalizedTarget = normalizeColor(targetColor); // 目标颜色与原始颜色一致时直接返回,避免无效操作 if (originalColor === normalizedTarget) return; const queue = [startPixel]; const gridContainer = startPixel.parentElement; const pixelSize = startPixel.offsetWidth; // 计算网格的行列数 const cols = Math.floor(gridContainer.offsetWidth / pixelSize); const rows = Math.floor(gridContainer.offsetHeight / pixelSize); // 获取像素在网格中的索引 const getPixelIndex = (pixel) => Array.from(gridContainer.children).indexOf(pixel); while (queue.length > 0) { const currentPixel = queue.shift(); // 二次检查颜色,防止已被其他路径修改 const currentColor = normalizeColor(currentPixel.style.backgroundColor); if (currentColor !== originalColor) continue; // 设置目标颜色 currentPixel.style.backgroundColor = normalizedTarget; const pixelIndex = getPixelIndex(currentPixel); const currentRow = Math.floor(pixelIndex / cols); const currentCol = pixelIndex % cols; // 遍历上下左右四个方向的相邻像素 const directions = [ { row: currentRow - 1, col: currentCol }, // 上 { row: currentRow + 1, col: currentCol }, // 下 { row: currentRow, col: currentCol - 1 }, // 左 { row: currentRow, col: currentCol + 1 } // 右 ]; for (const dir of directions) { // 边界校验:确保像素在网格范围内 if (dir.row >= 0 && dir.row < rows && dir.col >= 0 && dir.col < cols) { const neighborIndex = dir.row * cols + dir.col; const neighborPixel = gridContainer.children[neighborIndex]; const neighborColor = normalizeColor(neighborPixel.style.backgroundColor); // 颜色匹配则加入队列 if (neighborColor === originalColor) { queue.push(neighborPixel); } } } } }
3. 绑定点击事件(示例)
给每个像素绑定点击触发油漆桶功能:
document.querySelectorAll('.pixel').forEach(pixel => { pixel.addEventListener('click', () => { // 这里可以替换为你的颜色选择器返回的目标颜色 floodFill(pixel, '#ff0000'); }); });
原理说明
- 颜色标准化:通过临时元素的计算样式,把所有颜色转为小写十六进制格式,确保同色不同字符串的情况能正确匹配。
- BFS遍历逻辑:
- 以点击的像素为种子,加入队列。
- 逐个取出队列中的像素,替换颜色后,检查上下左右四个相邻像素。
- 符合颜色条件且在网格边界内的像素,加入队列继续处理。
- 避免重复/无效操作:
- 初始判断目标颜色与原始颜色是否一致,直接跳过。
- 从队列取出像素时二次检查颜色,防止同一像素被多次加入队列后重复处理。
- 边界控制:通过计算网格行列数,确保只遍历有效范围内的像素,避免访问不存在的DOM元素。
内容的提问来源于stack exchange,提问作者Mohammad Kabalan
相关产品推荐
相关产品推荐

