同一div绑定keydown与keyup事件异常:计时器仅首次可用
问题:空格键控制计时器仅首次有效,后续操作失效
需求与异常表现
需求:松开空格键启动计时器,按下空格键停止计时器。
异常:首次操作正常,后续按空格无法正常控制计时器,逻辑完全失效。
原代码
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="/public/css/style.css"> <script src="/public/js/timer.js" defer></script> </head> <body> <section id="play-area" tabindex="-1"> <h1 id="timer">00:00</h1> </section> </body> </html>
JavaScript代码
const timer = document.querySelector('#timer') const playArea = document.querySelector('#play-area') let timerStarted = false // starts timer when spacebar is released playArea.addEventListener('keyup', (e) => { if (e.keyCode === 32 && !timerStarted) startTimer() }) // start timer function function startTimer() { // assigning time values to zero let [milliseconds, seconds, minutes] = [0, 0, 0] let Interval // actual timer code Interval = setInterval(() => { milliseconds++ if (milliseconds % 100 === 0) { milliseconds = 0 seconds++ } if (seconds % 60 === 0 && seconds !== 0) { seconds = 0 minutes++ } timer.innerText = `${minutes}:${seconds}.${milliseconds}` }, 10) playArea.addEventListener('keydown', (e) => { if (e.keyCode === 32) stopTimer(Interval) }) } function stopTimer(interval) { clearInterval(interval) timerStarted = true setTimeout(function () { timerStarted = false console.log(timerStarted) }, 500) console.log(timerStarted) }
问题原因
每次调用startTimer()时,都会给playArea重复绑定keydown事件监听器。第一次启动后绑定1个监听器,第二次启动绑定第2个,以此类推。按下空格时,所有绑定的监听器都会触发:多次执行clearInterval,同时反复修改timerStarted状态,直接导致逻辑彻底混乱。
修复后的代码
修改后的JavaScript代码
const timer = document.querySelector('#timer') const playArea = document.querySelector('#play-area') let isTimerRunning = false // 标记计时器是否正在运行 let timerInterval = null // 全局存储计时器实例 let [milliseconds, seconds, minutes] = [0, 0, 0] // 时间变量提至全局,保留计时状态(如需每次启动重置可移回startTimer) // 只绑定一次keyup和keydown事件 playArea.addEventListener('keyup', (e) => { if (e.key === ' ' && !isTimerRunning) { startTimer() } }) playArea.addEventListener('keydown', (e) => { if (e.key === ' ' && isTimerRunning) { stopTimer() } }) function startTimer() { isTimerRunning = true // 如需每次启动重置时间,取消下面注释 // [milliseconds, seconds, minutes] = [0, 0, 0] timerInterval = setInterval(() => { milliseconds++ if (milliseconds % 100 === 0) { milliseconds = 0 seconds++ } if (seconds % 60 === 0 && seconds !== 0) { seconds = 0 minutes++ } // 格式化时间显示,补零更美观 const formattedMinutes = String(minutes).padStart(2, '0') const formattedSeconds = String(seconds).padStart(2, '0') const formattedMs = String(milliseconds).padStart(2, '0') timer.innerText = `${formattedMinutes}:${formattedSeconds}.${formattedMs}` }, 10) } function stopTimer() { clearInterval(timerInterval) isTimerRunning = false // 防抖:500ms内禁止再次启动,避免误触 setTimeout(() => { isTimerRunning = false }, 500) }
关键优化点
- 全局仅绑定一次keyup/keydown事件,彻底避免重复绑定导致的逻辑冲突
- 用
isTimerRunning明确标记计时器状态,逻辑更清晰,替代原有的timerStarted - 将计时器实例
timerInterval和时间变量提至全局,方便事件监听器直接访问 - 优化时间显示格式,补零后更规范
- 保留防抖逻辑,避免短时间内重复触发启动
内容的提问来源于stack exchange,提问作者Umar Zahid
相关产品推荐
相关产品推荐

