hover文件夹弹出上层菜单时元素快速闪烁问题排查
问题描述
我是前端开发初学者,代码组织可能不够规范,也可能遗漏了一些显而易见的问题。
受代码片段运行环境的安全策略限制,无法加载图片资源(甚至不会显示加载失败图标),完整可运行示例已托管在CodePen平台(按规则不放置跳转外链)。
- 异常表现:鼠标hover文件夹元素时交互正常,但当鼠标移动到hover文件夹时弹出的菜单上时,菜单会出现快速闪烁现象
- 预期效果:用户hover文件夹时显示弹出菜单,鼠标离开文件夹区域时菜单自动销毁
相关实现代码
HTML代码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Something</title> <link href="style.css" rel="stylesheet" type="text/css" /> <link href='https://fonts.googleapis.com/css?family=Poppins' rel='stylesheet'> </head> <body> <!-- See CSS code for more explanation --> <div class="sidebar"> <h1 style="padding: 1vw;">Todo</h1> <div class="folder"> Folder 1 </div> <div class="folder"> Folder 2 </div> <div class="folder"> Folder 3 </div> <div class="addBtn">+ Folder</div> </div> <script src="script.js"></script> </body> </html>
CSS代码
/* might have something to do with css; please read my hastily made comments */ /* self explanatory */ html,body { height: 100%; margin: 0px; padding: 0px; background-color: black; } /* sets some defaults */ * { padding: 0px; margin: 0px; border: 0px; overflow: hidden; font-family: Poppins; background-color: inherit; } /* sets text selection color to nothing */ *::selection { background: inherit; } /* styling for the sidebar (the part that says "todo", shows folders, etc.) */ .sidebar { background: #9caeb0; display: inline-block; width: calc(20% - 2vw); height: 100vh; padding: 1vw; } /* the text that says todo */ h1 { font-size: 30px; font-weight: 700; } /* a folder. */ .folder { width: calc(15.4vw); background-color: #8c9ca3; padding: .6vw; padding-left: 1.25vw; padding-right: 1.25vw; border-radius: 0px 5px 5px 0px; font-weight: 200; cursor: pointer; transition: .45s; margin: .6vw; margin-left: -1vw; margin-right: calc(0vw); font-size: 15px; position: relative; } /* uses css animations to change the folder upon hovering */ .folder:hover { background-color: #75828a; cursor: pointer; margin-top: .8vw; margin-bottom: .8vw; margin-left: -2vw; padding-left: 2.25vw; width: 15.8vw; font-size: 17px; } /* the add folder button */ .addBtn { width: 15.4vw; background-color: rgba(0,0,0,0); padding: .6vw; padding-left: 1.25vw; padding-right: 1.25vw; border-radius: 0px 5px 5px 0px; font-weight: 200; cursor: pointer; transition: .45s; margin-left: -1vw; font-size: 15px; border: 3px solid #8c9ca3; border-left: 0; /*position: absolute; bottom: 4vh;*/ } /* changes bg color upon hovering over add folder button */ .addBtn:hover { background-color: #8c9ca3; } .smallMenu { position: absolute; height: 17px; top: 50%; width: 17px; -ms-transform: translateY(-50%); transform: translateY(-50%); right: 0.5vw; border-radius: 99px; } .menuBtn { position: absolute; height: 14px; top: 1.5px; left: 7px; padding: 0px; margin: 0px; }
JavaScript代码
// probably can ignore these functions; scroll down to line 45; there's a lot of code here for purposes that I haven't quite finished yet function inLocalStorage(item) { if(!localStorage.getItem(item)) { return false; } else { return true; } } function lsAdd(label,value) { localStorage.setItem(label, value); } function lsGet(item) { return localStorage.getItem(item); } function lsClear() { localStorage.clear(); } function lsRemove(item) { localStorage.removeItem(item); } var el; var mouseOver = false; // checks if the user has visited if (!inLocalStorage('visited')) { alert("Don't mind this alert"); lsAdd('visited','yes'); lsAdd('folders','1'); lsAdd('js',''); } else { // load from local storage; execute stored JS new Function(lsGet('js'))(); // upon mouseover of folder, show mini menu icon for (let i = 0; i < document.getElementsByClassName("folder").length; i++) { document.getElementsByClassName("folder")[i].addEventListener("click", function() { console.log("You have clicked."); }); // add menu upon mouseover document.getElementsByClassName("folder")[i].addEventListener("mouseover", function() { mouseOver = false; el = document.createElement("div"); // this image obviously doesn't load but that's not important; the image is an svg with three vertical dots, and the image is transparent el.innerHTML = '<div class="smallMenu"><img src="abc.svg" class="menuBtn" style="height:14px;"></div>'; el.setAttribute("id","menu"); document.getElementsByClassName('folder')[i].appendChild(el); el = document.getElementsByClassName('smallMenu')[0]; el.addEventListener('mouseover', function() { mouseOver = true; console.log(mouseOver); }); el.addEventListener('mouseout', function() { mouseOver = false; console.log(mouseOver); }); }); // remove menu upon mouse out document.getElementsByClassName("folder")[i].addEventListener("mouseout", function() { if (mouseOver === false) { el = document.getElementById("menu"); el.remove(); } }); } }
问题原因
菜单闪烁的核心原因有两个:
- 事件选择错误:你用的
mouseover/mouseout是冒泡事件,当鼠标从文件夹移动到子元素菜单上时,会先触发文件夹的mouseout执行菜单删除逻辑,菜单删除后鼠标又落回文件夹区域,再次触发mouseover重新创建菜单,循环往复就出现了连续闪烁。 - 全局变量冲突:所有文件夹共用同一个
mouseOver标记和el元素变量,多个文件夹的事件触发时会互相覆盖变量值,状态判断完全混乱。
修复方案
- 替换事件类型:把
mouseover/mouseout换成不冒泡的mouseenter/mouseleave,鼠标移动到当前元素的子节点上时,不会触发父元素的离开事件。 - 废弃全局变量:每个文件夹的事件逻辑单独维护自己的菜单元素引用,避免变量互相覆盖。
- 增加重复创建判断:菜单已经存在时不再重复生成DOM。
修复后的核心JS逻辑参考:
for (let i = 0; i < document.getElementsByClassName("folder").length; i++) { const folder = document.getElementsByClassName("folder")[i]; let menuEl = null; folder.addEventListener("click", function() { console.log("You have clicked."); }); folder.addEventListener("mouseenter", function() { if (menuEl) return; menuEl = document.createElement("div"); menuEl.innerHTML = '<div class="smallMenu"><img src="abc.svg" class="menuBtn" style="height:14px;"></div>'; folder.appendChild(menuEl); }); folder.addEventListener("mouseleave", function() { if (menuEl) { menuEl.remove(); menuEl = null; } }); }
额外优化:这个菜单显隐逻辑完全可以用纯CSS实现,不需要写JS:只要把菜单元素预先写在文件夹DOM结构里,默认设置display: none,再加规则.folder:hover .smallMenu { display: block; }就能实现效果,性能更好逻辑也更简单。
内容的提问来源于stack exchange,提问作者typhon
相关产品推荐
相关产品推荐

