如何用纯JS实现鼠标移出div后保留ul li标签的hover类
问题根因
你的代码核心问题出在两个地方:
- 将
mouseover事件绑定到了全局document对象,鼠标在页面任意位置移动都会触发处理函数。当鼠标移出下拉容器后,事件触发源e.target不再是下拉选项li,处理函数会直接把非li元素设为激活对象,原有选中li的hover类就会被移除。 - 没有对事件触发源做有效性校验,非上下方向键触发时,不管当前触发元素是不是下拉选项,都会直接替换激活对象。
修复后的完整代码
JavaScript部分
// 先获取下拉容器和所有选项元素 const dropdownContainer = document.querySelector('.dropdown-test'); const dropdownItems = document.querySelectorAll('.dropdownItemContainer li'); // 初始化激活项 let active = document.querySelector(".hover") || dropdownItems[0]; // 只给下拉选项绑定mouseover事件,不要绑定全局 dropdownItems.forEach(item => { item.addEventListener('mouseover', handler); }); // 方向键事件绑定到下拉容器,保证只有容器聚焦时才响应,避免和页面其他快捷键冲突 dropdownContainer.addEventListener('keydown', handler); // 给容器加tabindex使其可以聚焦,支持键盘操作 dropdownContainer.setAttribute('tabindex', '-1'); function handler(e){ // 先存旧的激活项 const oldActive = active; if (e.type === 'keydown') { // 用e.key替代废弃的e.which,兼容性更好 if (e.key === 'ArrowDown') { // 向下选,没有下一个就停在最后一个 active = active.nextElementSibling || dropdownItems[dropdownItems.length - 1]; // 阻止页面默认滚动行为 e.preventDefault(); } else if (e.key === 'ArrowUp') { // 向上选,没有上一个就停在第一个 active = active.previousElementSibling || dropdownItems[0]; e.preventDefault(); } // 优化:选中项自动滚动到可视区域,适配可滚动下拉场景 active.scrollIntoView({ block: 'nearest' }); } else if (e.type === 'mouseover') { // 只有鼠标悬停在选项上时,才把当前li设为激活项 active = e.target; } // 只有激活项真的变了才修改类,减少不必要的DOM操作 if (oldActive !== active) { oldActive.classList.remove('hover'); active.classList.add('hover'); } }
原有CSS和HTML不需要改动,保持你原来的写法即可。
核心逻辑说明
- 事件绑定范围限制:把鼠标悬停事件只绑定给下拉选项li,鼠标移出下拉区域后不会触发相关逻辑,自然不会修改激活项的类名,实现选中状态稳定保留。
- 事件类型区分处理:键盘事件和鼠标事件分开判断,只有对应的合法操作才会修改激活项,避免误触发。
- 激活项有效性保证:所有修改激活项的逻辑都限定在下拉选项的范围内,不会出现激活项变成非li元素的情况。
内容的提问来源于stack exchange,提问作者Pocket
相关产品推荐
相关产品推荐

