如何用JavaScript实现100vh整屏逐页滚动效果?
实现每次滚动切换100vh页面的解决方案
你的核心需求是让页面每次滚动时直接切换到下一个/上一个完整视口高度的位置,但原代码存在几个关键问题:
- 在
scroll事件里调用scrollTo会触发无限循环——因为scrollTo会再次触发scroll事件,导致代码反复执行 - 视口高度的计算冗余:
Math.round(window.innerHeight / (100/100))等价于window.innerHeight,完全没必要多此一举 vh+vh的更新逻辑会让值翻倍,不是每次增加一个视口高度,导致滚动位置错误
正确实现方案
下面是基于React的完整实现,解决了上述问题,同时适配窗口大小变化:
import { useState, useEffect } from 'react'; function FullPageScroll() { // 存储当前视口高度 const [viewportHeight, setViewportHeight] = useState(window.innerHeight); // 跟踪当前处于第几页(从0开始计数) const [currentPage, setCurrentPage] = useState(0); // 监听窗口大小变化,实时更新视口高度 useEffect(() => { const handleResize = () => setViewportHeight(window.innerHeight); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // 监听滚轮事件,处理页面切换 useEffect(() => { const handleWheel = (e) => { // 阻止浏览器原生滚动,避免干扰自定义逻辑 e.preventDefault(); // 判断滚动方向:deltaY>0是向下滚,反之向上 const isScrollDown = e.deltaY > 0; // 计算总页数(根据页面总内容高度/视口高度) const totalPages = Math.ceil(document.body.scrollHeight / viewportHeight); // 计算新的页面索引,避免超出边界 let newPage = currentPage; if (isScrollDown && newPage < totalPages - 1) { newPage += 1; } else if (!isScrollDown && newPage > 0) { newPage -= 1; } // 滚动到目标位置,可选平滑滚动效果 window.scrollTo({ top: newPage * viewportHeight, behavior: 'smooth' }); setCurrentPage(newPage); }; // passive: false 是为了能调用preventDefault window.addEventListener('wheel', handleWheel, { passive: false }); return () => window.removeEventListener('wheel', handleWheel); }, [currentPage, viewportHeight]); return ( <div> {/* 示例页面内容,每个section占满100vh */} <section style={{ height: '100vh', backgroundColor: '#ff4444', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '2rem' }}>页面1</section> <section style={{ height: '100vh', backgroundColor: '#00C851', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '2rem' }}>页面2</section> <section style={{ height: '100vh', backgroundColor: '#33b5e5', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '2rem' }}>页面3</section> </div> ); } export default FullPageScroll;
关键细节说明
- 用
wheel事件替代scroll事件:避免了scrollTo触发无限循环的问题,直接监听用户的滚动动作而非页面滚动结果 - 边界控制:计算总页数并限制页面索引范围,防止滚动超出内容边界
- 窗口大小适配:监听
resize事件更新视口高度,保证窗口缩放后滚动位置依然准确 - 可选平滑滚动:
behavior: 'smooth'可以实现过渡效果,不需要的话可以删除该属性,改为即时滚动
移动端适配补充
如果需要支持移动端触摸滑动,可以在上述useEffect中添加触摸事件逻辑:
// 在handleWheel的useEffect里添加以下代码 let touchStartY = 0; const handleTouchStart = (e) => { touchStartY = e.touches[0].clientY; }; const handleTouchEnd = (e) => { const touchEndY = e.changedTouches[0].clientY; // 判断滑动方向:手指从下往上滑(touchStartY - touchEndY > 50)是切换到下一页 const isSwipeDown = touchStartY - touchEndY > 50; const isSwipeUp = touchEndY - touchStartY > 50; const totalPages = Math.ceil(document.body.scrollHeight / viewportHeight); let newPage = currentPage; if (isSwipeDown && newPage < totalPages - 1) { newPage += 1; } else if (isSwipeUp && newPage > 0) { newPage -= 1; } window.scrollTo({ top: newPage * viewportHeight, behavior: 'smooth' }); setCurrentPage(newPage); }; window.addEventListener('touchstart', handleTouchStart); window.addEventListener('touchend', handleTouchEnd); // 清理事件时也要移除触摸事件 return () => { window.removeEventListener('wheel', handleWheel); window.removeEventListener('touchstart', handleTouchStart); window.removeEventListener('touchend', handleTouchEnd); };
内容的提问来源于stack exchange,提问作者hossein azadpoor
相关产品推荐
相关产品推荐

