JavaScript画板模式切换问题:hover-draw与click-draw互斥失效
问题:画板hover/click绘图模式切换失效
我正在开发一个画板功能,希望支持用户在hover-draw模式(默认模式,鼠标悬停在div上时改变其颜色)和click-draw模式(需点击或按住鼠标拖动div才能上色)之间切换。
我添加了一个按钮用于切换hoverOn/clickOn变量的值,期望只有当对应变量处于"on"状态时,相应的绘图函数才会生效。
但实际情况是,点击按钮将hoverOn设为false后,hover事件函数仍会执行,div依然会被上色。
完整代码
HTML
<body> <div class="main"> <h1>Draw something nice!</h1> <div class="content"> <div class="settings"> <div class="grid-resize"> <label for="grid-resize">Change the grid size:</label> <input type="range" id="grid-resize" min="4" max="100"> </div> <div class="buttons"> <button id="clear" class="settings-buttons" title="Click here to clear the board">Clear All</button> <button id="reset" class="settings=buttons" title="Click here to go back to original settings - this will clear the board">Reset</button> <button id="change-mode" class="settings-buttons" title="Click here to change the mode">Draw Mode</button> <button id="random-color" class="settings-buttons">Random Color</button> <button id="black-shades" class="settings-buttons">Black Shades</button> <button id="eraser" class="settings-buttons"><img src="images/rubber.svg">Eraser</button> </div> </div> <div class="container"> </div> </div> </div> </body>
CSS
html, body { margin: 0; padding: 20px; width: 100%; height: 100%; box-sizing: border-box; text-align: center; } h1 { margin-top: 0; } .content { display: flex; justify-content: center; gap: 5em; } .content .settings { display: flex; flex-direction: column; width: 15%; } .content .grid-resize { text-align: start; margin-bottom: 2em; } .content .buttons { display: flex; flex-wrap: wrap; gap: 1.5em; } .content .buttons button { border: none; box-shadow: 1px 1px rgba(128, 128, 128, 0.41); border-radius: 15px; width: 70px; height: 70px; } .content .buttons button:hover { box-shadow: 2px 2px #176fae; background-color: #72A0C1; color: white; transform: scale(1.2); } .content .buttons button:active { box-shadow: none; background-color: #72A0C1; color: white; transform: scale(1); } .content .buttons button .eraser-border { border: 1px solid black; } .container { display: grid; width: 400px; height: 400px; } .container .cell { background-color: white; border: 1px solid rgba(220, 220, 220, 0.3); }
JavaScript
const container = document.querySelector('.container'); const newSize = document.querySelector('#grid-resize'); const clearBtn = document.querySelector('#clear'); const resetBtn = document.querySelector('#reset'); const randomBtn = document.querySelector('#random-color'); const modeBtn = document.querySelector('#change-mode'); const eraser = document.querySelector('#eraser'); let size = 16; let hoverOn = true; let clickOn = false; let color = '#72A0C1'; newSize.addEventListener('input', changeSize); clearBtn.addEventListener('click', clearGrid); resetBtn.addEventListener('click', resetGrid); modeBtn.addEventListener('click', changeMode); randomBtn.addEventListener('click', drawRainbow); eraser.addEventListener('click', () => { if (color == 'white') { color = '#72A0C1'; eraser.style.border = 'none'; } else { color = 'white'; eraser.style.border = '2px dotted #FFC0CB'; } }); let isMousedown = false; container.addEventListener('mousedown', ()=>{isMousedown = true;}); container.addEventListener('mouseup', ()=>{isMousedown = false;}); function changeSize() { const cells = document.querySelectorAll('.cell'); cells.forEach(cell => { cell.remove(); }); size = newSize.value; generateDiv(size); } function clearGrid() { const cells = document.querySelectorAll('.cell'); cells.forEach(cell => { cell.style.backgroundColor = 'white'; }); } function resetGrid() { const cells = document.querySelectorAll('.cell'); cells.forEach(cell => { cell.remove(); }); size = 16; newSize.value = size; generateDiv(); } function changeMode() { if (hoverOn) { hoverOn = false; clickOn = true; } else if (clickOn) { clickOn = false; hoverOn = true; } console.log (hoverOn, clickOn) } function setHoverMode() { if (hoverOn == true) { container.addEventListener('mouseover', function (e) { e.target.style.background = `${color}`; }); } } function setClickMode() { if (clickOn == true) { container.addEventListener('mousemove', (e) => { if (isMousedown) { e.target.style.backgroundColor = `${color}`; } }); container.addEventListener('mousemove', e => e.preventDefault()); } } function generateColor() { let randomColor = '#'; let characters = 'ABCDEF0123456789'; for(let i=0; i < 6; i++) { randomColor += characters.charAt(Math.floor(Math.random() * characters.length)); color = randomColor; } } function drawRainbow() { container.addEventListener('mousemove', ()=>{ generateColor(); }); } function generateDiv() { container.style.gridTemplateColumns = `repeat(${size}, 1fr)`; container.style.gridTemplateRows = `repeat(${size}, 1fr)`; for (let i = 0; i < size * size; i++) { let cell = document.createElement('div'); cell.classList.add('cell'); container.appendChild(cell); } } generateDiv(size);
解决方案
问题核心在于你当前的写法是一次性添加事件监听器,一旦监听器绑定到container上,后续修改hoverOn/clickOn变量并不会自动移除这些监听器,所以即使变量变为false,之前绑定的hover事件依然会触发。
修复步骤:
- 提取事件处理函数为独立变量:把hover和click模式的处理逻辑单独存成函数,方便后续添加/移除监听器。
- 在模式切换时动态添加/移除监听器:每次切换模式时,先移除不需要的模式对应的监听器,再添加当前模式的监听器。
- 初始化时绑定默认模式的监听器:页面加载时先绑定hover模式的监听器。
修改后的关键代码如下:
// 提取独立的事件处理函数 function handleHoverDraw(e) { // 确保目标是单元格 if (!e.target.classList.contains('cell')) return; if (hoverOn) { e.target.style.background = `${color}`; } } function handleClickDraw(e) { // 确保目标是单元格 if (!e.target.classList.contains('cell')) return; if (clickOn && isMousedown) { e.target.style.backgroundColor = `${color}`; } } // 修改changeMode函数,动态切换监听器 function changeMode() { if (hoverOn) { hoverOn = false; clickOn = true; // 移除hover监听器 container.removeEventListener('mouseover', handleHoverDraw); // 添加click模式监听器 container.addEventListener('mousemove', handleClickDraw); } else if (clickOn) { clickOn = false; hoverOn = true; // 移除click模式监听器 container.removeEventListener('mousemove', handleClickDraw); // 添加hover监听器 container.addEventListener('mouseover', handleHoverDraw); } console.log(hoverOn, clickOn); } // 初始化绑定默认的hover模式监听器 container.addEventListener('mouseover', handleHoverDraw);
另外,原代码中的setHoverMode和setClickMode函数没有被调用过,这两个函数可以直接删掉,改用上面的动态切换逻辑。
额外优化点:
drawRainbow函数存在重复绑定事件的问题,每次点击按钮都会新增一个mousemove监听器,建议改成切换一个rainbowMode变量,在统一的绘图处理函数里判断是否生成随机颜色。- 新增的单元格判断逻辑,避免鼠标误操作到容器本身导致样式异常。
内容的提问来源于stack exchange,提问作者pseudodejapris
相关产品推荐
相关产品推荐

