鼠标移出时:hover动画触发Intersection Observer入场动画重复执行问题
这个问题的核心根源是两个动画都依赖CSS animation 属性:当图片添加.show-bottom类时,会应用movefromtop入场动画;hover状态又会覆盖animation-name为hvr-buzz。当鼠标移出,hover的动画样式被移除,浏览器会重新解析.show-bottom中的animation规则——虽然animation-fill-mode: forwards保留了动画最终状态,但浏览器会默认重新触发该动画执行,导致入场效果重复播放。
而用transform: scale/rotate不会出问题,是因为这些是直接修改transform属性,不属于animation规则的覆盖逻辑,也就不会触发动画重置。
下面给出两种简单有效的解决方案:
方案1:将入场动画改为CSS Transition(推荐)
把入场的位移动画从animation改成transition,让入场效果成为属性过渡,和hover的animation互不干扰。hover结束后,元素只会平滑过渡回初始状态,不会触发入场动画重复执行。
修改后的入场CSS代码:
.floating { opacity: 0; transform: translateY(-5em); /* 设置入场前的初始位置 */ transition: opacity 1s ease, transform 1s ease; /* 同时过渡透明度和位移 */ } .floating.show-bottom { opacity: 1; transform: translateY(0em); /* 设置入场后的最终位置 */ } /* 移除原来的@keyframes movefromtop,不再需要 */
hover动画的代码保持不变即可。这样当鼠标移出时,hvr-buzz动画停止,元素会通过transition平滑过渡回.show-bottom的状态,完全避免了动画冲突。
方案2:标记动画完成状态,阻止重复触发
如果坚持要用animation实现入场效果,可以给元素添加一个额外类(比如animation-completed)标记入场动画已完成,然后在CSS中仅对未完成的元素应用入场动画:
修改后的入场CSS:
.floating { opacity: 0; transition: 1s opacity; } /* 仅当元素有.show-bottom且无.animation-completed时,才执行入场动画 */ .floating.show-bottom:not(.animation-completed) { opacity: 1; animation: movefromtop 1s alternate; animation-iteration-count: 1; animation-fill-mode: forwards; } @keyframes movefromtop { from { transform: translateY(-5em); } to { transform: translateY(0em); } }
修改JavaScript,在入场动画结束后添加标记类:
在show函数中,监听动画结束事件,给元素添加完成标记:
function show(e) { return new Promise((res, rej) => { setTimeout(() => { e.classList.add(classToAdd); // 监听动画结束,添加完成标记后移除监听 const handleAnimationEnd = () => { e.classList.add('animation-completed'); e.removeEventListener('animationend', handleAnimationEnd); }; e.addEventListener('animationend', handleAnimationEnd); res(); }, delay); }); }
这样入场动画完成后,元素就有了animation-completed类,即使hover结束后浏览器重新解析样式,也不会再触发movefromtop动画了。
两种方案都能解决你的问题,方案1更简洁且兼容性更好,推荐优先使用。
内容的提问来源于stack exchange,提问作者Pedro Corchero Murga

