如何用GSAP ScrollTrigger实现滚动时多Div底部固定并逐段展示内容?
问题描述
需要使用GSAP的ScrollTrigger插件实现以下垂直滚动动画效果:
- 首次滚动时,5个标题全部可见
- 继续滚动,第1个标题向上滚动,对应段落按高度逐步显示,其余4个标题固定在窗口底部
- 第1个标题和段落完全显示后,第2个标题向上滚动并显示对应段落,以此类推
条件要求:
- 每个Div包含1个标题和1个段落
- 默认状态下标题可见,段落隐藏
注意事项:第5个Div的标题必须固定在底部,直到第4个Div的动画完成后才可滚动
解决方案
实现逻辑
- 通过
position: sticky将列表容器固定在视口内,让标题默认排列在视口底部 - 为前4个列表项创建独立的ScrollTrigger时间线,控制标题上移、段落展开,以及序号和线条的颜色变化
- 最后一个列表项默认用固定定位锁定在底部,待前4个动画完成后,再切换为相对定位并执行自身的展开动画
- 每个动画阶段占用100vh的滚动范围,确保滚动节奏清晰
完整代码
<section style="background:black; height: 100vh"></section> <div class="provider-benefit"> <div class="container-xl"> <div class="row justify-content-xl-end"> <div class="col-12"> <div class="provider-benefit-list" id="pb_list"> <div class="list-item"> <span class="list-item-no">1</span> <span class="list-item-line"></span> <div class="list-item-wrapper"> <div class="list-item-header"> <h3 class="list-item-title">Lorem Ipsum 1</h3> </div> <div class="list-item-body"> <p class="list-item-content">1. Lorem ipsum is placeholder text commonly used in the graphic.</p> </div> </div> </div> <div class="list-item"> <span class="list-item-no">2</span> <span class="list-item-line"></span> <div class="list-item-wrapper"> <div class="list-item-header"> <h3 class="list-item-title">Lorem Ipsum 2</h3> </div> <div class="list-item-body"> <p class="list-item-content">2. Lorem ipsum is placeholder text</p> </div> </div> </div> <div class="list-item"> <span class="list-item-no">3</span> <span class="list-item-line"></span> <div class="list-item-wrapper"> <div class="list-item-header"> <h3 class="list-item-title">Lorem Ipsum 3</h3> </div> <div class="list-item-body"> <p class="list-item-content">3. Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups</p> </div> </div> </div> <div class="list-item"> <span class="list-item-no">4</span> <span class="list-item-line"></span> <div class="list-item-wrapper"> <div class="list-item-header"> <h3 class="list-item-title">Lorem Ipsum 4</h3> </div> <div class="list-item-body"> <p class="list-item-content">4. Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups</p> </div> </div> </div> <div class="list-item"> <span class="list-item-no">5</span> <span class="list-item-line"></span> <div class="list-item-wrapper"> <div class="list-item-header"> <h3 class="list-item-title">Lorem Ipsum 5</h3> </div> <div class="list-item-body"> <p class="list-item-content">5. Lorem ipsum is placeholder text commonly used in the graphic, print, and publishing industries for previewing layouts and visual mockups</p> </div> </div> </div> <!-- list items end --> </div> </div> </div> </div> </div> <section style="background:black; height: 100vh" id=""></section> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js" defer="defer"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js" defer="defer"></script> <script> document.addEventListener("DOMContentLoaded", function() { gsap.registerPlugin(ScrollTrigger); const listItems = document.querySelectorAll('.list-item'); const lastItem = listItems[listItems.length - 1]; let totalScrollOffset = 0; // 处理前4个列表项的滚动动画 listItems.forEach((item, index) => { if (index === 4) return; const header = item.querySelector('.list-item-header'); const body = item.querySelector('.list-item-body'); const no = item.querySelector('.list-item-no'); const line = item.querySelector('.list-item-line'); const bodyHeight = body.scrollHeight; const tl = gsap.timeline({ scrollTrigger: { trigger: '.provider-benefit-list', start: `top+=${totalScrollOffset}vh top`, end: `top+=${totalScrollOffset + 100}vh top`, scrub: true, ease: 'linear' } }); tl.to(header, { y: -bodyHeight, duration: 0.5 }) .to(body, { height: bodyHeight, opacity: 1, duration: 0.5 }, 0) .to(no, { backgroundColor: "#04B7A9", duration: 0.3 }, 0) .to(line, { backgroundColor: "#04B7A9", duration: 0.3 }, 0); totalScrollOffset += 100; }); // 释放最后一个固定的列表项 gsap.to(lastItem, { position: 'relative', bottom: 'auto', left: 'auto', right: 'auto', scrollTrigger: { trigger: '.provider-benefit-list', start: `top+=${totalScrollOffset}vh top`, end: `top+=${totalScrollOffset + 100}vh top`, scrub: true, ease: 'linear' } }); // 最后一个列表项的内容展开动画 const lastHeader = lastItem.querySelector('.list-item-header'); const lastBody = lastItem.querySelector('.list-item-body'); const lastNo = lastItem.querySelector('.list-item-no'); const lastLine = lastItem.querySelector('.list-item-line'); const lastBodyHeight = lastBody.scrollHeight; const lastTl = gsap.timeline({ scrollTrigger: { trigger: '.provider-benefit-list', start: `top+=${totalScrollOffset}vh top`, end: `top+=${totalScrollOffset + 100}vh top`, scrub: true, ease: 'linear' } }); lastTl.to(lastHeader, { y: -lastBodyHeight, duration: 0.5 }) .to(lastBody, { height: lastBodyHeight, opacity: 1, duration: 0.5 }, 0) .to(lastNo, { backgroundColor: "#04B7A9", duration: 0.3 }, 0) .to(lastLine, { backgroundColor: "#04B7A9", duration: 0.3 }, 0); }); </script> <style> .provider-benefit { position: relative; background-repeat: no-repeat; background-size: 43%; background-position: -15.5% 51.7%; counter-reset: listnumber; min-height: 500vh; } .provider-benefit-img { max-width: 600px; width: 100%; } .provider-benefit-list { padding-left: var(--size_30_half); position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; justify-content: flex-end; } .provider-benefit .list-item { position: relative; padding-left: 60px; padding-right: var(--size_10); margin-bottom: var(--size_20); overflow: hidden; } .provider-benefit .list-item-no { position: absolute; left: 0; top: 0; display: block; text-align: center; line-height: 40px; width: 40px; height: 40px; background-color: #c4c4c4; border-radius: 50%; color: var(--clr_primary); font-size: var(--font_24_22_20_18); font-weight: 700; z-index: 1; transition: all linear 0.5s; } .provider-benefit .list-item-line { position: absolute; left: 18px; top: 0; display: block; width: 4px; height: 100%; background-color: #d9d9d9; z-index: 0; transition: all linear 0.5s; } .provider-benefit .list-item-body { height: 0; overflow: hidden; transition: height 0.3s ease; } .provider-benefit .list-item-title { font-size: var(--font_26_24_22_20_bs); font-weight: 600; } .provider-benefit .list-item-content { margin-bottom: 0; font-size: var(--font_20_18_16_14_bs); font-weight: 400; max-width: 550px; } .provider-benefit .list-item.active::before { background-color: var(--clr_primary_blue); } .provider-benefit .list-item.active::after { background-color: var(--clr_primary); } .provider-benefit .list-item.active .list-item-body { height: 100%; transform: translateY(0%); opacity: 1; } .provider-benefit .list-item:last-child { position: fixed; bottom: var(--size_20); left: calc(var(--size_30_half) + 60px); right: var(--size_10); } </style>
内容的提问来源于stack exchange,提问作者Vimal Chavda
相关产品推荐
相关产品推荐

