JavaScript访问未创建元素及Etch-a-Sketch颜色修改故障排查
Etch-a-Sketch项目:网格单元格颜色修改问题及解决
我正在完成TheOdinProject的Etch-a-Sketch项目,想要修改网格单元格的颜色,但这些单元格是在函数内部的for循环中创建的。搜索相关问题没找到合适的解决方案,或者看不懂相关内容。
创建网格的原函数代码
function changeGridSize(number) { let container = document.getElementById(`container`); totalNumber = number * number; while (container.hasChildNodes()) { container.removeChild(container.firstChild); } for (let i = 0; i < totalNumber; i++) { let gridSquare = document.createElement("div"); gridSquare.classList.add("grid_square"); let cellwidth = container.clientWidth / number + `px`; let cellheight = container.clientHeight / number + `px`; gridSquare.setAttribute( `style`, `width: ${cellwidth}; height: ${cellheight};` ); container.appendChild(gridSquare); } }
修改颜色的原代码
let color = document.getElementById(`colour_picker`); color.addEventListener(`change`, changeColor() ); let choosenColour = color.value; function changeColor() { while (container.hasChildNodes()) { gridSquare.setAttribute(`style`, `background-color: ${choosenColour};`) } }
问题现象
预期效果是用户选择颜色后,网格单元格背景色改为所选颜色,但添加change事件监听器后页面崩溃,且网格尺寸调整到40x40以上时也会崩溃。
补充HTML代码
<div id="outer_container"> <div id="container"> </div> </div> <div id="controls"> <input type="color" class="user_input" name="" id="colour_picker" value="#000000"> <div class="slider"> <div class="toggle_change" id="arrow_down"></div> <input style="margin-bottom: 0;" class="user_input" type="range" min="2" max="100" value="16" name="options" id="grid_range"> <div class="toggle_change" id="arrow_up"></div> </div> </div>
问题分析与修复方案
1. 事件监听的致命错误
原代码里的color.addEventListener('change', changeColor());直接调用了changeColor函数,页面加载时就会执行这个函数,而函数里的while (container.hasChildNodes())是无限循环——因为你只修改单元格样式,没有移除节点,hasChildNodes()永远为true,直接导致页面卡死崩溃。
修复:
把事件监听改成传函数引用,去掉括号:
color.addEventListener('change', changeColor);
2. 变量作用域与遍历逻辑错误
container和gridSquare是changeGridSize里的局部变量,changeColor函数根本访问不到。choosenColour只在页面初始化时获取了一次颜色值,用户后续选择新颜色时不会更新。- 用while循环遍历单元格的逻辑完全错误,应该直接获取所有网格单元格元素并逐个修改样式。
修复后的changeColor函数:
let color = document.getElementById('colour_picker'); color.addEventListener('change', changeColor); function changeColor() { const choosenColour = color.value; // 每次触发事件时获取最新选中的颜色 const container = document.getElementById('container'); const gridSquares = container.querySelectorAll('.grid_square'); // 获取所有带grid_square类的单元格 gridSquares.forEach(square => { // 不要用setAttribute覆盖整个style,避免冲掉宽高属性,直接修改backgroundColor square.style.backgroundColor = choosenColour; }); }
3. 大网格性能优化
当网格尺寸调大(比如40x40=1600个单元格)时,原代码每次循环都直接向DOM添加节点,频繁触发浏览器重绘重排,导致页面卡顿甚至崩溃。
修复:
使用DocumentFragment批量添加节点,减少DOM操作次数:
function changeGridSize(number) { const container = document.getElementById('container'); const totalNumber = number * number; container.innerHTML = ''; // 替代while循环移除子节点,更简洁高效 const fragment = document.createDocumentFragment(); // 创建文档片段,暂存所有单元格 const cellWidth = `${container.clientWidth / number}px`; const cellHeight = `${container.clientHeight / number}px`; for (let i = 0; i < totalNumber; i++) { const gridSquare = document.createElement('div'); gridSquare.classList.add('grid_square'); gridSquare.style.width = cellWidth; gridSquare.style.height = cellHeight; fragment.appendChild(gridSquare); } container.appendChild(fragment); // 一次性把所有单元格添加到容器,只触发一次DOM更新 }
另外,原代码里的totalNumber没有用let/const声明,会变成全局变量,可能引发其他问题,修复时加上了const。
内容的提问来源于stack exchange,提问作者Muzzu153
相关产品推荐
相关产品推荐

