
本文详解如何使用 gsap scrolltrigger 实现水平滚动容器内子 div 的逐项吸附效果——首次进入时自动吸附至第一个子项,滚动过程中依次吸附至第二、第三个子项,且末项不触发吸附,同时支持反向滚动时准确回弹至前一项。
本文详解如何使用 gsap scrolltrigger 实现水平滚动容器内子 div 的逐项吸附效果——首次进入时自动吸附至第一个子项,滚动过程中依次吸附至第二、第三个子项,且末项不触发吸附,同时支持反向滚动时准确回弹至前一项。
在 GSAP 水平滚动场景中,常见的“滚动即吸附”问题往往源于 scrollTrigger 的 trigger 和 snap 配置未与滚动容器的可视区域动态对齐。直接对父容器 .horizontal_container 设置固定 start: "top" 并配合静态 snapTo: [0, 0.5, 1],会导致吸附点偏移——因为 GSAP 默认将 snap 值解释为 整个滚动距离的百分比,而非每个子元素的视口对齐位置,从而造成首尾项吸附失效或反向滚动跳变。
✅ 正确解法:动态计算每个子元素的滚动锚点,并结合 pin: true 与 scrub 实现平滑吸附
以下是经过验证的完整实现方案(适配 3 个子 div 场景,可扩展):
// 获取容器与子元素
const container = document.querySelector(".horizontal_container");
const sections = gsap.utils.toArray(".section"); // 假设每个子 div 类名为 section
// 计算总滚动宽度:各子项宽度之和(含 gap)
const totalWidth = sections.reduce((sum, el) => sum + el.offsetWidth, 0);
const containerWidth = container.offsetWidth;
// 创建滚动动画
gsap.to(sections, {
x: () => -(totalWidth - containerWidth), // 向左滚动总距离
ease: "none",
scrollTrigger: {
trigger: container,
pin: true,
scrub: 1,
start: "top top",
end: () => `+=${totalWidth}`,
snap: {
snapTo: sections.map((_, i) => (i / (sections.length - 1)) * 100), // 百分比位置:[0%, 50%, 100%]
duration: { min: 0.2, max: 0.6 },
delay: 0.1,
ease: "power2.inOut"
}
}
});? 关键要点说明:
-
snapTo使用sections.map(...)动态生成吸附点(如[0, 50, 100]),单位为 滚动进度百分比(0% → 起始,100% → 结束),确保每个子项中心严格对齐视口; -
end: () => "+=" + totalWidth精确匹配滚动总长度,避免因+=3000等硬编码导致吸附范围失准; -
scrub: 1启用实时滚动同步,配合snap实现“松手即吸附”,而非仅靠scrollTrigger自动触发; - 若需最后一项不吸附(如题中要求),可将
snapTo改为仅前两项:snapTo: sections.slice(0, -1).map((_, i) => (i / (sections.length - 2)) * 100) // → [0, 100] for 2 items → 即只吸附第1、第2个子项,第3个自然滑过
⚠️ 注意事项:
- 所有
.section元素必须为display: inline-block或float: left/flex布局,确保水平流式排列; - 避免在
.horizontal_container上设置overflow: hidden(GSAP pin 会自动处理),否则可能截断滚动内容; - 开发阶段开启
markers: true可直观调试start/end位置,上线前务必移除; - 移动端需额外添加
touch-action: pan-y到容器以防止手势冲突。
通过动态锚点计算 + 精确滚动范围控制,即可彻底解决“首项不吸附”“反向回弹错位”等典型问题,实现专业级水平滚动体验。

















