You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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;

关键细节说明

  1. 用wheel事件替代scroll事件:避免了scrollTo触发无限循环的问题,直接监听用户的滚动动作而非页面滚动结果
  2. 边界控制:计算总页数并限制页面索引范围,防止滚动超出内容边界
  3. 窗口大小适配:监听resize事件更新视口高度,保证窗口缩放后滚动位置依然准确
  4. 可选平滑滚动: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:44:56