如何在React中实现类似David-hckh作品集的100vh区块局部滚动效果?
实现固定视口内内容滚动、区块整体滚动的效果
核心思路
这个效果是分阶段的滚动逻辑:
- 第一阶段:页面主滚动时,About区块固定占满100vh视口,内部超长内容仅在区块范围内滚动
- 第二阶段:内部内容滚动到底部后,继续滚动页面,整个About区块随页面向上移出视口
具体实现方案
1. React组件代码实现
import { useState, useEffect, useRef } from 'react'; const AboutSection = () => { const [contentScrolledToEnd, setContentScrolledToEnd] = useState(false); const contentRef = useRef(null); const sectionRef = useRef(null); // 监听内部内容滚动,判断是否到底部 useEffect(() => { const checkScrollEnd = () => { const content = contentRef.current; if (!content) return; // 加10px容差避免滚动精度问题 const isBottom = content.scrollTop + content.clientHeight >= content.scrollHeight - 10; setContentScrolledToEnd(isBottom); }; const content = contentRef.current; content?.addEventListener('scroll', checkScrollEnd); return () => content?.removeEventListener('scroll', checkScrollEnd); }, []); // 监听页面滚动,动态切换区块定位方式 useEffect(() => { const handlePageScroll = () => { if (!contentScrolledToEnd) return; const section = sectionRef.current; if (!section) return; const sectionInitialTop = section.offsetTop; // 页面滚动超过区块初始位置时,切换为相对定位让区块随页面滚动 if (window.scrollY > sectionInitialTop) { section.style.position = 'relative'; section.style.top = 'auto'; } else { section.style.position = 'fixed'; section.style.top = '0'; } }; window.addEventListener('scroll', handlePageScroll); return () => window.removeEventListener('scroll', handlePageScroll); }, [contentScrolledToEnd]); return ( <> <section ref={sectionRef} style={{ position: contentScrolledToEnd ? 'relative' : 'fixed', top: contentScrolledToEnd ? 'auto' : '0', left: '0', width: '100%', height: '100vh', overflow: 'hidden', }} > <div style={{ display: 'flex', width: '100%', height: '100%' }}> {/* 左侧固定视觉区域(可放图片/3D场景) */} <div style={{ flex: 1, backgroundColor: '#f0f0f0' }}></div> {/* 右侧可滚动内容区 */} <div ref={contentRef} style={{ flex: 1, height: '100vh', overflowY: 'auto', padding: '2rem', boxSizing: 'border-box', }} > <h2>About Me</h2> {/* 这里放你的超长介绍内容,重复足够多让它超过视口高度 */} <p>这里是我的个人介绍...</p> <p>更多介绍内容...</p> {/* 重复内容省略 */} </div> </div> </section> {/* 页面后续区块 */} <section style={{ minHeight: '100vh', padding: '2rem', backgroundColor: '#fff' }}> 页面其他内容区域 </section> </> ); }; export default AboutSection;
2. 可选CSS优化(自定义滚动条)
/* 自定义右侧内容区的滚动条样式 */ .about-content::-webkit-scrollbar { width: 6px; } .about-content::-webkit-scrollbar-thumb { background-color: #888; border-radius: 3px; } .about-content::-webkit-scrollbar-track { background-color: #f1f1f1; }
3. 关键逻辑说明
- 初始状态:About区块用
position: fixed固定在视口,高度100vh,内部内容容器开启overflowY: auto,实现内容在区块内独立滚动 - 内容滚动监听:判断内容是否滚动到底部,切换状态标记
- 页面滚动监听:当内容滚到底后,若页面滚动超过区块初始位置,将区块定位从
fixed改为relative,此时区块会进入文档流,随页面一起滚动;滚动回顶部时恢复固定定位
你之前尝试无效的原因
设置min-height: 100vh加overflow: scroll的问题在于:
- 没有区分「区块固定」和「区块滚动」两个阶段的定位逻辑,单纯的overflow只会让内容在区块内滚动,但区块本身始终占据文档流位置,无法实现"内容滚完后区块才随页面滚动"的效果
- 必须通过JavaScript监听滚动状态,动态切换区块定位方式,才能实现分阶段的滚动效果
内容的提问来源于stack exchange,提问作者Miguel Pedraza
相关产品推荐
相关产品推荐

