求助:实现Section底部距顶部33vh时粘性定位的异常问题
问题描述
我想要实现这样的滚动过渡效果:当每个section的底部距离屏幕顶部33vh时,该section变为sticky状态,后续section滚动覆盖它。但因为各section高度不固定,基于div底部到屏幕顶部的距离设置粘性定位时出现了问题:在提供的代码示例中,Section 1被截断,末尾有大量空白,Section 4完全不可见,只有Section 2和Section 3之间的过渡符合预期。优先要纯CSS/Tailwind解决方案,不过项目是React构建且用Framer Motion做动画,也接受其他可行方案。
原代码示例
CSS
.container { text-align: center; height: 100vh; overflow-y: auto; background: black; margin: 0; } .section1 { position: sticky; top: 0px; display: flex; justify-content: space-between; height: 100vh; background: linear-gradient(180deg, blue, orange); flex-direction: column; /* hack to get top section to not be covered up by transform */ margin-bottom: 220vh; } .section { /* translate up so that position is 0 when the last 33vh of div is visible */ transform: translateY(calc(-100% + 33vh)); position: sticky; top: 0px; height: 240vh; display: flex; flex-direction: column; justify-content: space-between; background: linear-gradient(180deg, blue, orange); } .flex-div { display: flex; flex-direction: column; }
HTML
<div class="container"> <div class="section1"> <div class="flex-div"> <h1>Section 1 top</h1> </div> <div class="flex-div"> <h1>Section 1 bottom</h1> </div> </div> <div class="section"> <div class="flex-div"> <h1>Section 2 top</h1> </div> <div class="flex-div"> <h1>Section 2 bottom</h1> </div> </div> <div class="section"> <div class="flex-div"> <h1>Section 3 top</h1> </div> <div class="flex-div"> <h1>Section 3 bottom</h1> </div> </div> <div class="section"> <div class="flex-div"> <h1>Section 4 top</h1> </div> <div class="flex-div"> <h1>Section 4 bottom</h1> </div> </div> </div>
解决方案
纯CSS方案
核心思路
原代码的问题在于使用transform和固定高度/外边距的hack,导致布局错位。正确的做法是给每个section套一个容器,通过容器高度提供足够的滚动空间,让sticky定位自然触发:
- 每个section容器高度设为
calc(100vh + 67vh):100vh是section占满屏幕的高度,67vh是从section底部到屏幕顶部33vh所需的滚动距离 - section设置
position: sticky; top: 0; min-height: 100vh;,确保滚动到触发点时粘在顶部,后续section覆盖它
代码示例
CSS
.container { text-align: center; background: black; margin: 0; } .section-wrapper { height: calc(100vh + 67vh); } .section { position: sticky; top: 0; min-height: 100vh; display: flex; flex-direction: column; justify-content: space-between; background: linear-gradient(180deg, blue, orange); } .flex-div { display: flex; flex-direction: column; }
HTML
<div class="container"> <div class="section-wrapper"> <div class="section"> <div class="flex-div"> <h1>Section 1 top</h1> </div> <div class="flex-div"> <h1>Section 1 bottom</h1> </div> </div> </div> <div class="section-wrapper"> <div class="section"> <div class="flex-div"> <h1>Section 2 top</h1> </div> <div class="flex-div"> <h1>Section 2 bottom</h1> </div> </div> </div> <div class="section-wrapper"> <div class="section"> <div class="flex-div"> <h1>Section 3 top</h1> </div> <div class="flex-div"> <h1>Section 3 bottom</h1> </div> </div> </div> <div class="section-wrapper"> <div class="section"> <div class="flex-div"> <h1>Section 4 top</h1> </div> <div class="flex-div"> <h1>Section 4 bottom</h1> </div> </div> </div> </div>
Tailwind CSS方案
直接用Tailwind类替换CSS规则,实现相同效果:
<div class="bg-black m-0 text-center"> <div class="h-[calc(100vh+67vh)]"> <div class="sticky top-0 min-h-screen flex flex-col justify-between bg-gradient-to-b from-blue-500 to-orange-500"> <div class="flex flex-col"> <h1>Section 1 top</h1> </div> <div class="flex flex-col"> <h1>Section 1 bottom</h1> </div> </div> </div> <div class="h-[calc(100vh+67vh)]"> <div class="sticky top-0 min-h-screen flex flex-col justify-between bg-gradient-to-b from-blue-500 to-orange-500"> <div class="flex flex-col"> <h1>Section 2 top</h1> </div> <div class="flex flex-col"> <h1>Section 2 bottom</h1> </div> </div> </div> <div class="h-[calc(100vh+67vh)]"> <div class="sticky top-0 min-h-screen flex flex-col justify-between bg-gradient-to-b from-blue-500 to-orange-500"> <div class="flex flex-col"> <h1>Section 3 top</h1> </div> <div class="flex flex-col"> <h1>Section 3 bottom</h1> </div> </div> </div> <div class="h-[calc(100vh+67vh)]"> <div class="sticky top-0 min-h-screen flex flex-col justify-between bg-gradient-to-b from-blue-500 to-orange-500"> <div class="flex flex-col"> <h1>Section 4 top</h1> </div> <div class="flex flex-col"> <h1>Section 4 bottom</h1> </div> </div> </div> </div>
React + Framer Motion方案
利用Framer Motion的滚动监听能力,添加平滑过渡动画,同时保留sticky布局逻辑:
import { motion, useScroll, useTransform } from "framer-motion"; export default function StickySectionDemo() { const { scrollYProgress } = useScroll(); return ( <div className="bg-black m-0 text-center text-white"> {[1, 2, 3, 4].map((sectionNum) => { // 计算每个section的滚动触发区间 const start = (sectionNum - 1) * 1.67; const end = sectionNum * 1.67; // 滚动到覆盖区域时,当前section逐渐淡出 const opacity = useTransform(scrollYProgress, [start, start + 0.33], [1, 0]); return ( <div key={sectionNum} className="h-[calc(100vh+67vh)]"> <motion.div className="sticky top-0 min-h-screen flex flex-col justify-between bg-gradient-to-b from-blue-500 to-orange-500" style={{ opacity }} transition={{ duration: 0.3 }} > <div className="flex flex-col pt-8"> <h1>Section {sectionNum} top</h1> </div> <div className="flex flex-col pb-8"> <h1>Section {sectionNum} bottom</h1> </div> </motion.div> </div> ); })} </div> ); }
内容的提问来源于stack exchange,提问作者Josie
相关产品推荐
相关产品推荐

