JavaScript click事件单次点击触发两次的原因及解决方法
问题原因
单次点击触发两次回调是label默认交互行为+事件冒泡共同导致的:
- 你将click事件绑定在父容器
.button-container上,所有子元素的点击事件都会冒泡到该容器触发回调 - 容器内的
<label>标签和隐藏checkbox input关联,浏览器原生逻辑为:点击label时会自动触发关联input的点击事件 - 单次点击操作会产生两次冒泡到父容器的点击事件:第一次来自label/滑块元素本身,第二次来自label触发的input点击,两次回调连续执行就会出现「添加dark-mode类→马上移除dark-mode类」的情况,最终看不到切换效果,控制台也会输出两次日志。
- 额外问题:你注释掉的
classList.toggle写法存在错误,类名里多写了分号'dark-mode;',就算事件只触发一次也无法正确匹配目标类名。
解决方案
推荐优先使用第一种方案,逻辑最严谨也最符合表单开关组件的交互规范:
方案1:直接监听checkbox的change事件
将事件绑定在实际的表单控件上,通过控件的选中状态判断是否开启深色模式,从根源避免重复触发问题:
const toggleInput = document.querySelector('.switch input'); const bodyEl = document.body; toggleInput.addEventListener('change', function () { // 第二个参数传布尔值,可直接根据选中状态添加/移除类,比if判断更简洁 bodyEl.classList.toggle('dark-mode', this.checked); console.log('切换成功,当前深色模式状态:', this.checked); });
方案2:保留父容器事件绑定,过滤重复触发
如果需要将事件绑定在.button-container上,可在回调中判断事件源,只响应一次有效点击:
const btnContainer = document.querySelector('.button-container'); const bodyEl = document.body; btnContainer.addEventListener('click', function (e) { // 过滤掉label触发input带来的重复冒泡事件 if (e.target.tagName === 'SPAN') return; bodyEl.classList.toggle('dark-mode'); console.log('切换成功'); });
参考代码(修复后可直接运行)
HTML
<div class="toggle-wrapper"> <span>B&W</span> <div class="button-container"> <label class="switch"> <input type="checkbox"> <span class="slider"></span> </label> </div> <span>Color</span> </div>
CSS
.dark-mode { background-color: red; border: solid 20px yellow; } .toggle-wrapper { display: flex; align-items: center; justify-content: center; font-size: 2rem; } .switch { position: relative; display: inline-block; width: 60px; height: 30px; margin-left: 1rem; margin-right: 1rem; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: 0.4s; transition: 0.4s; } .slider:before { position: absolute; content: ''; height: 22px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: 0.4s; transition: 0.4s; } input:checked + .slider { background-color: #2196f3; } input:focus + .slider { box-shadow: 0 0 1px #2196f3; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); }
内容的提问来源于stack exchange,提问作者CodePlanB1234
相关产品推荐
相关产品推荐

