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

For-of循环导致鼠标悬停事件首次失效问题求助

问题:鼠标悬停事件首次触发无响应,第二次才生效的原因及解决方法

正常运行的代码示例

JavaScript

let allDivs = [...document.querySelectorAll(`div`)];

let Classes = [
    "A0", "A1", "A2", "A3"
];

console.log(`let 'allDivs' equals:`);
console.log(allDivs);

for (let i = 0; i < Classes.length; i++) {
    let allDivsFiltered = allDivs.filter(item => (item.className.match(`A${i}`)));

    console.log('---- ---- ---- ----');
    console.log(`let 'allDivsFiltered [${i}]' equals:`);
    console.log(allDivsFiltered);

    for (let all of allDivsFiltered) {
        all.onmouseover = () => {
            for (let a = 0; a < allDivsFiltered.length; a++) {
                allDivsFiltered[a].classList.add(`B${i}`);
            };
        };

        console.log(all);
    };
};

CSS

body {
    display: flex;
    flex-wrap: wrap;
}

div {
    position: relative;
    background-color: slategray;
    width: 100px;
    height: 100px;
    top: 0px;
    left: 0px;
    margin-top: 4px;
    margin-left: 4px;
}

.B0 {
    background-color: red;
}

.B1 {
    background-color: blue;
}

.B2 {
    background-color: green;
}

.B3 {
    background-color: yellow;
}

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <link rel="stylesheet" href="a.css">
</head>
<body>
    <div class="A0"></div>
    <div class="A1"></div>
    <div class="A2"></div>
    <div class="A1"></div>
    <div class="A2"></div>
    <div class="A0"></div>
    <div class="A3"></div>
    <div class="A0"></div>
    <div class="A3"></div>
    <script src="BBB.js"></script>
</body>
</html>

出现问题的代码示例(仅第二次鼠标悬停时span才变色)

JavaScript

const important = [...document.getElementsByClassName('important')];

console.log(`const 'important' equals:`);
console.log(important);
console.log('---- ---- ---- ----');

const Classes = [
    "color-c0", "color-c1", "color-c2", "color-c3",
    "color-c4", "color-c5", "color-c6", "color-c7",
];

const Entries = [
    "c0-enter", "c1-enter", "c2-enter", "c3-enter",
    "c4-enter", "c5-enter", "c6-enter", "c7-enter",
];

const Leaves = [
    "c0-leave", "c1-leave", "c2-leave", "c3-leave",
    "c4-leave", "c5-leave", "c6-leave", "c7-leave",
];

for (let i = 0; i < important.length; i++) {
    const allBlocks = [...document.querySelectorAll(`span[id *= block-A${i}] span[class *= color-c]`)];

    for (let each of allBlocks) {
        each.onmouseover = (e) => {
            let currentBlock = e.target.parentNode;
            let currentGroup = [...currentBlock.querySelectorAll(`span[class *= color-c]`)];

            console.log(`const 'currentGroup' of 'currentBlock [${i}]' equals:`);
            console.log(currentGroup);
            console.log('---- ---- ---- ----');

            for (let a = 0; a < Classes.length; a++) {
                let filteredGroups = currentGroup.filter(span => (span.className.match(`color-c${a}`)));

                console.log(`let 'filteredGroup [${a}]' of 'currentBlock [${i}]' equals:`);
                console.log(filteredGroups);
                console.log('---- ---- ---- ----');

                for (let all of filteredGroups) {
                    console.log(`this works the FIRST time!!`);
                    all.onmouseover = () => {
                        for (let b = 0; b < filteredGroups.length; b++) {
                            console.log(`this works the SECOND time!!`);
                            filteredGroups[b].classList.remove(`${Leaves[a]}`);
                            filteredGroups[b].classList.add(`${Entries[a]}`);
                        };
                    };

                    all.onmouseout = () => {
                        for (let b = 0; b <filteredGroups.length; b++) {
                            filteredGroups[b].classList.remove(`${Entries[a]}`);
                            filteredGroups[b].classList.add(`${Leaves[a]}`);
                        };
                    };
                };
            };
        };
    };
};

CSS

.c0-enter {
    animation: c0-enter 332ms ease 0s 1 normal forwards;
}

@keyframes c0-enter {
    0% {
        background-color: red;
        color: black;
    }
    100% {
        background-color: red;
        color: white;
    }
}

.c0-leave {
    animation: c0-leave 0.3s ease 0s 1 normal forwards;
}

@keyframes c0-leave {
    0% {
        color: white;
        background-color: red;
    }
    100% {
        color: unset;
        background-color: unset;
    }
}

/* DIVIDER */

.c1-enter {
    animation: c1-enter 332ms ease 0s 1 normal forwards;
}

@keyframes c1-enter {
    0% {
        background-color: blue;
        color: black;
    }
    100% {
        background-color: blue;
        color: white;
    }
}

.c1-leave {
    animation: c1-leave 0.3s ease 0s 1 normal forwards;
}

@keyframes c1-leave {
    0% {
        color: white;
        background-color: blue;
    }
    100% {
        color: unset;
        background-color: unset;
    }
}

/* DIVIDER */

.c2-enter {
    animation: c2-enter 332ms ease 0s 1 normal forwards;
}

@keyframes c2-enter {
    0% {
        background-color: black;
        color: black;
    }
    100% {
        background-color: black;
        color: white;
    }
}

.c2-leave {
    animation: c2-leave 0.3s ease 0s 1 normal forwards;
}

@keyframes c2-leave {
    0% {
        color: white;
        background-color: black;
    }
    100% {
        color: unset;
        background-color: unset;
    }
}

/* DIVIDER */

.c3-enter {
    animation: c3-enter 332ms ease 0s 1 normal forwards;
}

@keyframes c3-enter {
    0% {
        background-color: rgb(94, 94, 120);
        color: black;
    }
    100% {
        background-color: rgb(94, 94, 120);
        color: white;
    }
}

.c3-leave {
    animation: c3-leave 0.3s ease 0s 1 normal forwards;
}

@keyframes c3-leave {
    0% {
        color: white;
        background-color: rgb(94, 94, 120);
    }
    100% {
        color: unset;
        background-color: unset;
    }
}

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
    <link rel="stylesheet" href="a.css">
</head>
<body>
    <span id="block-A0" class="important">
        <p style="text-align: center; font-size: 40px; margin-top: 20%; cursor: default;">
            <span class="color-c0">Color 1</span>
            <span class="color-c1">Color 2</span>
            <span class="color-c2">Color 3</span>
            <span class="color-c3">Color 4</span>
        </p>
    </span>
    <script src="a.js"></script>
</body>
</html>

原因分析

核心问题是事件绑定时机错误,和for-of循环本身无关:

  • 你把filteredGroups元素的onmouseover/onmouseout事件绑定逻辑,嵌套在了外层each.onmouseover的回调函数里。
  • 第一次鼠标悬停时,仅执行了外层的mouseover回调,完成了内层事件的绑定,但并未触发实际的动画切换逻辑。
  • 第二次鼠标悬停时,已绑定好的内层mouseover事件才会执行,因此出现首次无响应的现象。

解决方法

直接在初始化阶段完成所有事件绑定,不需要嵌套在另一个mouseover回调中。修改后的JavaScript代码如下:

const important = [...document.getElementsByClassName('important')];

console.log(`const 'important' equals:`);
console.log(important);
console.log('---- ---- ---- ----');

const Entries = [
    "c0-enter", "c1-enter", "c2-enter", "c3-enter",
    "c4-enter", "c5-enter", "c6-enter", "c7-enter",
];

const Leaves = [
    "c0-leave", "c1-leave", "c2-leave", "c3-leave",
    "c4-leave", "c5-leave", "c6-leave", "c7-leave",
];

for (let i = 0; i < important.length; i++) {
    const allBlocks = [...document.querySelectorAll(`span[id *= block-A${i}] span[class *= color-c]`)];

    // 直接遍历目标元素绑定事件,无需外层mouseover触发
    for (let each of allBlocks) {
        // 匹配当前元素对应的color-c索引
        const match = each.className.match(/color-c(\d)/);
        if (!match) continue;
        const colorIndex = parseInt(match[1]);
        
        each.onmouseover = () => {
            // 获取当前block下的同组元素
            const currentBlock = each.closest(`span[id *= block-A${i}]`);
            const sameColorSpans = [...currentBlock.querySelectorAll(`span.color-c${colorIndex}`)];
            
            sameColorSpans.forEach(item => {
                item.classList.remove(Leaves[colorIndex]);
                item.classList.add(Entries[colorIndex]);
            });
        };

        each.onmouseout = () => {
            const currentBlock = each.closest(`span[id *= block-A${i}]`);
            const sameColorSpans = [...currentBlock.querySelectorAll(`span.color-c${colorIndex}`)];
            
            sameColorSpans.forEach(item => {
                item.classList.remove(Entries[colorIndex]);
                item.classList.add(Leaves[colorIndex]);
            });
        };
    };
};

优化点说明

  1. 移除嵌套的mouseover绑定逻辑,直接在初始化阶段给目标span绑定事件。
  2. 通过正则匹配直接获取当前span对应的color-c索引,避免循环遍历所有类名。
  3. 使用closest方法精准定位所属的block容器,替代原有的parentNode方式。
  4. 用forEach替代for循环,简化代码逻辑。

内容的提问来源于stack exchange,提问作者user26545514

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 15:14:52