You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
            }
        });
    }
}
问题原因

菜单闪烁的核心原因有两个:

  1. 事件选择错误:你用的mouseover/mouseout是冒泡事件,当鼠标从文件夹移动到子元素菜单上时,会先触发文件夹的mouseout执行菜单删除逻辑,菜单删除后鼠标又落回文件夹区域,再次触发mouseover重新创建菜单,循环往复就出现了连续闪烁。
  2. 全局变量冲突:所有文件夹共用同一个mouseOver标记和el元素变量,多个文件夹的事件触发时会互相覆盖变量值,状态判断完全混乱。
修复方案
  1. 替换事件类型:把mouseover/mouseout换成不冒泡的mouseenter/mouseleave,鼠标移动到当前元素的子节点上时,不会触发父元素的离开事件。
  2. 废弃全局变量:每个文件夹的事件逻辑单独维护自己的菜单元素引用,避免变量互相覆盖。
  3. 增加重复创建判断:菜单已经存在时不再重复生成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:30:45