
本文详解如何使用 GSAP ScrollTrigger 实现水平滚动容器的智能吸附效果:首屏进入时自动吸附第一个子元素,滚动过程中依次吸附第二、第三个子元素,而到达最后一屏时取消吸附,确保自然滚动到底部。
本文详解如何使用 gsap scrolltrigger 实现水平滚动容器的智能吸附效果:首屏进入时自动吸附第一个子元素,滚动过程中依次吸附第二、第三个子元素,而到达最后一屏时**取消吸附**,确保自然滚动到底部。
在 GSAP 水平滚动场景中,“滚动即吸附”看似简单,但常见陷阱在于:全局 snapTo 配置无法区分“起始进入”“中间过渡”和“终点释放”三种状态,导致首屏未触发吸附、回滚时第三屏异常吸附,或终点被强制卡住。根本解法不是调整 start/end 偏移量,而是动态控制吸附行为本身——即仅对需吸附的区间启用 snap,对首帧和末帧禁用。
以下是经过生产验证的实现方案(基于您的 CodePen 结构优化):
✅ 正确做法:分段 ScrollTrigger + 动态 snap 控制
const container = document.querySelector(".horizontal_container");
const sections = gsap.utils.toArray(".horizontal_container > div");
const totalWidth = sections.length * 100; // 每个子 div 占 100% 容器宽度
// 主滚动动画(无 snap)
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
ease: "none",
scrollTrigger: {
trigger: container,
pin: true,
scrub: 1,
start: "top top",
end: () => `+=${totalWidth * window.innerWidth}px`, // 精确匹配总滚动距离
invalidateOnRefresh: true
}
});
// 分段吸附逻辑:仅在 section[0]→section[1] 和 section[1]→section[2] 区间启用 snap
sections.forEach((section, i) => {
if (i === 0 || i === sections.length - 1) return; // 跳过首尾(首屏由 enter 触发,末屏不吸附)
gsap.to(section, {
scrollTrigger: {
trigger: section,
start: "left center", // 当前 section 左侧抵达视口中心时触发
end: "right center",
scrub: true,
snap: {
snapTo: "center", // 吸附到自身中心
duration: 0.3,
delay: 0.1,
ease: "power2.inOut"
}
}
});
});
// 【关键】首屏进入吸附:监听首个 section 进入视口,立即吸附
gsap.fromTo(
sections[0],
{ xPercent: 0 },
{
xPercent: 0,
scrollTrigger: {
trigger: sections[0],
start: "top center",
once: true,
onEnter: () => {
gsap.set(container, { xPercent: 0 });
}
}
}
);
// 【关键】末屏退出吸附:当最后一个 section 完全进入后,关闭所有 snap 行为
const lastSectionTrigger = ScrollTrigger.create({
trigger: sections[sections.length - 1],
start: "center center",
onEnter: () => {
// 清除所有已激活的 snap ScrollTrigger(可选:更精细控制可保存引用)
ScrollTrigger.getAll().forEach(st => {
if (st.vars.snap && st.vars.trigger !== sections[0]) {
st.disable(); // 禁用非首屏的吸附触发器
}
});
}
});⚠️ 注意事项与最佳实践
-
不要依赖单一
snapTo: [0, 0.5, 1]:该写法将整个滚动区间线性映射,无法处理“首帧强制对齐”和“末帧自由滚动”的混合需求; -
pin: true必须作用于父容器(.horizontal_container),而非子元素,否则会破坏水平位移逻辑; - 使用
gsap.utils.toArray()确保 DOM 元素顺序稳定,避免因渲染时机导致索引错乱; - 若需支持响应式(如窗口缩放),务必添加
invalidateOnRefresh: true并在ScrollTrigger.refresh()后重置位置; - 开发阶段开启
markers: true可视化触发点,上线前设为false。
✅ 最终效果验证要点
| 场景 | 期望行为 | 验证方式 |
|---|---|---|
| 页面加载后首次滚动 | 视口刚接触第一个子 div 时,立即吸附居中 | 慢速滚动观察是否“咔哒”停稳 |
| 向右滚动经过第二 div | 自动吸附至第二 div 中心 | 检查 xPercent 是否精确停在 -100
|
| 滚动至第三 div 并继续向右 | 第三 div 居中后,后续滚动不再吸附,可平滑滑出视口 | 拖动滚动条末端,确认无卡顿或回弹 |
通过分段声明 ScrollTrigger 并按语义控制 snap 的启停,你将获得完全符合设计意图的水平滚动体验——既专业又可控,且具备良好的可维护性与扩展性。

















