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

NextJS结合Material UI实现Scrollspy:元素视口检测始终返回false

解决NextJS+Material UI中Scrollspy的isInViewport判断失效问题

问题根源

你当前的isInViewport函数判断的是元素完全处于视口内,条件过于严格。而Scrollspy的核心需求是元素部分进入视口就触发状态更新,这导致即使元素已经可见,函数仍返回false。

修复方案

1. 修正isInViewport的可见性判断逻辑

将完全可见的条件改为判断元素与视口存在交集(部分可见即可):

function isInViewport(selector: string) {
  const el = document.querySelector(selector);
  if (!el) return false;

  const rect = el.getBoundingClientRect();
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;

  // 判断元素是否与视口有交集(部分可见即满足)
  return (
    rect.top <= viewportHeight &&
    rect.bottom >= 0 &&
    rect.left <= viewportWidth &&
    rect.right >= 0
  );
}

2. 优化滚动事件处理

  • 添加节流函数减少scroll事件触发频率,提升性能
  • 补全useEffect的事件清理逻辑,避免内存泄漏
  • 初始加载时主动触发一次检查,确保页面刚加载时状态正确

修改后的Scrollspy组件:

import React from 'react';

function isInViewport(selector: string) {
  const el = document.querySelector(selector);
  if (!el) return false;

  const rect = el.getBoundingClientRect();
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;

  return (
    rect.top <= viewportHeight &&
    rect.bottom >= 0 &&
    rect.left <= viewportWidth &&
    rect.right >= 0
  );
}

// 简单实现节流函数
const throttle = (func: (...args: any[]) => void, delay: number) => {
  let lastExecuteTime = 0;
  return (...args: any[]) => {
    const now = Date.now();
    if (now - lastExecuteTime >= delay) {
      func(...args);
      lastExecuteTime = now;
    }
  };
};

export default function Scrollspy(props: any) {
  const [navigationValue, setNavigationValue] = React.useState<string>('');

  const anchors = ["A", "B", "C", "D"];

  const scrollCheck = throttle(() => {
    // 倒序遍历锚点,确保当前最靠近视口底部的可见元素被选中(符合滚动逻辑)
    for (let i = anchors.length - 1; i >= 0; i--) {
      const anchor = anchors[i];
      if (isInViewport(`#${anchor}`)) {
        setNavigationValue(anchor);
        break; // 找到目标后立即停止遍历,避免不必要的计算
      }
    }
  }, 100); // 100ms节流间隔,可根据需求调整

  React.useEffect(() => {
    window.addEventListener('scroll', scrollCheck);
    // 页面加载完成后立即检查一次
    scrollCheck();
    // 组件卸载时移除事件监听
    return () => window.removeEventListener('scroll', scrollCheck);
  }, []);

  // ... 后续的导航菜单渲染逻辑
  return (
    <div>
      {/* 示例导航菜单,根据navigationValue高亮对应项 */}
      <nav>
        {anchors.map(anchor => (
          <a
            key={anchor}
            href={`#${anchor}`}
            style={{
              fontWeight: navigationValue === anchor ? 'bold' : 'normal',
              color: navigationValue === anchor ? '#1976d2' : 'inherit'
            }}
          >
            {anchor}
          </a>
        ))}
      </nav>
      {/* 页面内容区域 */}
      <div id="A" style={{ height: '100vh', backgroundColor: '#f0f0f0' }}>Section A</div>
      <div id="B" style={{ height: '100vh', backgroundColor: '#e0e0e0' }}>Section B</div>
      <div id="C" style={{ height: '100vh', backgroundColor: '#d0d0d0' }}>Section C</div>
      <div id="D" style={{ height: '100vh', backgroundColor: '#c0c0c0' }}>Section D</div>
    </div>
  );
}

3. 额外说明

  • 倒序遍历锚点的原因:当多个锚点元素同时处于视口内时,确保当前滚动位置最下方的元素被选中,符合用户滚动时的视觉焦点逻辑。
  • 节流函数的作用:避免scroll事件高频触发导致的性能损耗,100ms的间隔是平衡响应速度和性能的常用值。

内容的提问来源于stack exchange,提问作者Horkos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:58:28