GSAP 动画冲突:鼠标快速滑动导致 Tween 失效
在 WordPress 或 Elementor 页面中使用 GSAP 制作卡片悬停动画时,慢速移动鼠标通常一切正常,但快速划过多张图片后,按钮和文字可能停在错误位置,甚至完全不再播放离开动画。
这不是 GSAP 失效,而是同一个元素在短时间内收到多次动画指令,旧 Tween 尚未结束,新 Tween 又开始修改相同属性,最终形成动画竞争,这就是典型的 GSAP 动画冲突。
问题表现
- 鼠标快速经过多张卡片后,按钮没有回到初始位置。
- 文字透明度或位移状态停在动画中间。
- 再次进入卡片时动画突然跳动,没有从当前状态平滑衔接。
- 桌面端偶发,鼠标移动越快越容易复现。
问题本质:Tween Conflict
一次快速的进入和离开可能形成下面的执行顺序:
- 触发
pointerenter,GSAP 开始执行进入动画。 - 进入动画还没完成,鼠标已经离开。
- 离开动画又对同一元素的
transform或opacity写入新值。 - 多个 Tween 同时竞争相同属性,最终状态取决于最后一次更新。
解决方法一:使用 overwrite
给新的动画添加 overwrite:'auto',可以让 GSAP 自动停止与新动画冲突的旧属性 Tween。
| 设置 | 含义 | 适用情况 |
|---|---|---|
false | 允许旧动画继续执行 | 多个 Tween 修改不同属性 |
true | 停止目标上的其他 Tween | 需要强制重置全部动画 |
'auto' | 只处理与新 Tween 冲突的属性 | 大多数 hover 动画 |
gsap.to(button, {
y: 0,
autoAlpha: 1,
duration: 0.35,
ease: 'power2.out',
overwrite: 'auto'
});
解决方法二:开始新动画前清理旧 Tween
gsap.killTweensOf() 适合需要完全掌控状态的场景。每次进入或离开前,先停止相关元素尚未结束的动画,再创建新的 Tween。
gsap.killTweensOf([button, text]);
对于普通卡片数量,这个操作的性能开销通常可以忽略。真正需要避免的是在 pointermove 这类高频事件里不断创建大量 Tween。
完整的卡片悬停代码
下面的示例使用 transform 和透明度制作动画。请把类名替换成页面中真实的卡片、按钮和文字类名。
document.addEventListener('DOMContentLoaded', () => {
const cards = document.querySelectorAll('.szymwp-hover-card');
cards.forEach((card) => {
if (card.dataset.gsapHoverReady === '1') {
return;
}
const button = card.querySelector('.szymwp-hover-button');
const text = card.querySelector('.szymwp-hover-text');
if (!button || !text) {
return;
}
card.dataset.gsapHoverReady = '1';
gsap.set(button, { y: 24, autoAlpha: 0 });
gsap.set(text, { y: 0, autoAlpha: 1 });
const stopCurrentTweens = () => {
gsap.killTweensOf([button, text]);
};
card.addEventListener('pointerenter', () => {
stopCurrentTweens();
gsap.to(button, {
y: 0,
autoAlpha: 1,
duration: 0.35,
ease: 'power2.out',
overwrite: 'auto'
});
gsap.to(text, {
y: -14,
autoAlpha: 0,
duration: 0.25,
ease: 'power2.out',
overwrite: 'auto'
});
});
card.addEventListener('pointerleave', () => {
stopCurrentTweens();
gsap.to(button, {
y: 24,
autoAlpha: 0,
duration: 0.28,
ease: 'power2.inOut',
overwrite: 'auto'
});
gsap.to(text, {
y: 0,
autoAlpha: 1,
duration: 0.32,
ease: 'power2.out',
overwrite: 'auto'
});
});
});
});
为什么建议用 pointerenter 和 pointerleave?
mouseover 和 mouseout 会在鼠标经过卡片内部子元素时重复触发,容易额外创建 Tween。pointerenter 和 pointerleave 更适合整个卡片的进入与离开状态,同时也统一了鼠标、触控笔等指针事件。
在 Elementor 中使用时的注意事项
- GSAP 主库必须在自定义动画代码之前加载。
- 如果 Elementor 也给同一元素设置了 Entrance Animation,应关闭其中一方,避免同时修改 transform。
- 脚本可能在编辑器预览中重复初始化,所以示例使用
data-gsap-hover-ready防止重复绑定。 - 优先动画
transform和opacity,不要频繁改变top、bottom等会触发布局计算的属性。 - 更新代码后清除 WP Rocket、Elementor CSS 和浏览器缓存。
照顾减少动画偏好
对于启用了“减少动态效果”的用户,可以直接跳过动画并显示最终内容:
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
gsap.set('.szymwp-hover-button', { y: 0, autoAlpha: 1 });
}
常见问题
overwrite 和 killTweensOf 需要同时使用吗?
不一定。简单 hover 动画通常只用 overwrite:'auto' 就够了;状态较复杂或旧动画必须彻底停止时,再配合 killTweensOf()。
为什么代码正常但 Elementor 中仍然跳动?
检查 Elementor、主题 CSS 和 GSAP 是否同时修改了同一个元素的 transform 或 transition,并确认脚本没有被重复加载。




