导航栏hover效果中JavaScript实现的CSS动画仅生效一次的原因及替代方案
Nice catch! Let's break down why your JavaScript-bound mouseout animation only works once, then dive into way simpler (and more reliable) pure CSS solutions that eliminate this issue entirely.
Why the JavaScript Animation Only Runs Once
The problem boils down to how browsers handle inline styles and animations. When you run x.style.animation = 'hoverOut 1s forwards' on mouseout, that animation property gets written directly to the element's inline style attribute. After the first animation finishes, that inline animation value stays there.
Browsers only re-execute an animation if the animation property changes. Since the value is still hoverOut 1s forwards on the second mouseout, the browser sees no change and skips running the animation again. That's why you have to refresh to get it working once more.
Pure CSS Alternatives (No JavaScript Required)
You don't need any JS for this fade effect—CSS is built exactly for these kinds of state transitions, and it'll work flawlessly every time. Here are two great options:
Option 1: CSS Transitions (Simplest & Most Efficient)
Transitions are perfect for smooth fades between two static styles. Just define the transition on your base link style, then set the hover state. The browser handles both the hover-in and hover-out animations automatically:
ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333; } li { float: left; } li a { display: block; color: white; text-align: center; padding: 14px 16px; text-decoration: none; transition: background-color 0.5s ease; /* Smooth transition for color changes */ background-color: #333; /* Default state */ } li a:hover { background-color: #111; /* Hover state */ } .active { background-color: #222; }
Hover over a link, and it fades to #111 over 0.5 seconds. Move your mouse away, and it fades right back to #333—no extra code needed.
Option 2: CSS Keyframes (If You Need More Control)
If you want to stick with keyframes (maybe for more complex animations later), you can use CSS pseudo-classes to trigger them instead of JavaScript:
ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333; } li { float: left; } li a { display: block; color: white; text-align: center; padding: 14px 16px; text-decoration: none; background-color: #333; } /* Trigger hover animation on mouse enter */ li a:hover { animation: hoverIn 0.5s forwards; } /* Trigger exit animation on mouse leave */ li a:not(:hover) { animation: hoverOut 1s forwards; } @keyframes hoverIn { 0% { background-color: #333; } 100% { background-color: #111; } } @keyframes hoverOut { 0% { background-color: #111; } 100% { background-color: #333; } } .active { background-color: #222; }
This works reliably because the pseudo-classes (:hover and :not(:hover)) dynamically toggle the animation properties every time your mouse moves, so the browser always sees a change and runs the animation.
Fixing the JavaScript Approach (If You Must Use It)
If you have a specific reason to keep using JavaScript, you can reset the animation property after it finishes to make it run again. Here's how:
let navChildren = [...document.getElementById('nav-container').children] navChildren.forEach(x => { x.addEventListener('click', () => { }) x.addEventListener('mouseout', () => { // Reset the animation first to clear any existing inline style x.style.animation = 'none'; // Force a reflow to make the reset take effect void x.offsetWidth; // Re-apply the hoverOut animation x.style.animation = 'hoverOut 1s forwards'; // Clear the animation property once it finishes x.addEventListener('animationend', () => { x.style.animation = ''; }, { once: true }); // Use once: true to avoid multiple event listeners }) })
That said, the pure CSS methods are way cleaner, more maintainable, and less prone to bugs like the one you ran into.
内容的提问来源于stack exchange,提问作者Zain Wilson - WCH Student

