锚点跳转 + 滚动监听

准备工作

首先,我们要理解scrollY、innerHeight、offsetTop、offsetHeight这些概念。

这些属性都与 Web 开发中的滚动条位置、元素高度等相关。

核心思想是:计算每个菜单项对应的内容区域的位置范围,并将当前滚动条位置和内容区域位置进行比较,以找到当前所在的菜单项。

  • scrollY:表示文档在垂直方向上已经滚动的像素值,即滚动条距离页面顶部的距离,通常用于判断滚动条的位置。例如,window.scrollY 表示当前页面向下滚动的像素值

  • innerHeight:表示浏览器窗口的视口高度,即浏览器可见区域的高度,不包括滚动条和工具栏。例如,window.innerHeight 表示当前浏览器窗口的视口高度。

  • offsetTop:表示当前元素的上边缘相对于其 offsetParent 元素的上边缘的距离,即元素距离其父元素顶部的距离。例如,element.offsetTop 表示当前元素距离其 offsetParent 元素顶部的距离。

  • offsetHeight:表示当前元素的高度,包括元素的高度、上下内边距和边框。例如,element.offsetHeight 表示当前元素的高度,包括元素的上下内边距和边框。

需要注意的是,scrollYinnerHeight 属性是 window 对象的属性,而 offsetTopoffsetHeight 属性是元素对象的属性,可以通过 JavaScript 代码来获取这些属性的值。这些属性通常用于 JavaScript 中的滚动监听、元素高度计算等操作

代码如下:

index.js

javascript 复制代码
import React, { useState, useEffect, useRef } from 'react';

import './index.css';

function AnchorComponent() {
  const menuData = [
    { key: 'home', title: 'Home' },
    { key: 'about', title: 'About' },
    { key: 'services', title: 'Services' },
    { key: 'contact', title: 'Contact' },
  ];
  
  const contentData = [
    { key: 'home', title: 'Home', content: 'Home Content' },
    { key: 'about', title: 'About', content: 'About Content' },
    { key: 'services', title: 'Services', content: 'Services Content' },
    { key: 'contact', title: 'Contact', content: 'Contact Content' },
  ];

  const [currentMenuItem, setCurrentMenuItem] = useState('home');
  const contentRef = useRef(null);

  useEffect(() => {
    const handleScroll = () => {
      // 获取当前滚动条的位置
      const scrollPosition = window.scrollY;
      // 获取窗口可视区域的高度
      const windowHeight = window.innerHeight;

      // 计算每个菜单项对应的内容区域的位置范围
      contentData.forEach(content => {
        // 获取当前菜单项对应的内容区域元素
        const contentElement = document.getElementById(content.key);
        if (contentElement) {
          const contentPosition = contentElement.offsetTop;
          const contentHeight = contentElement.offsetHeight;

          /* 
            如果当前滚动条位置大于或等于当前菜单项对应的内容区域顶部位置减去窗口可视区域高度的一半,并且
            如果当前滚动条位置小于当前菜单项对应的内容区域底部位置减去窗口可视区域高度的一半
            则认为当前滚动条位置在当前菜单项对应的内容区域范围内,将当前菜单项设置为选中状态
          */
          if (scrollPosition >= contentPosition - windowHeight / 2 && scrollPosition < contentPosition + contentHeight - windowHeight / 2) {
            setCurrentMenuItem(content.key);
          }
        }
      });
    };

    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  const handleMenuItemClick = (key) => {
    const contentElement = document.getElementById(key);
    if (contentElement) {
      contentElement.scrollIntoView({
        behavior: 'smooth',
        block: 'start',
      });
    }
  };

  return (
    <div className="anchor-component-container">
      <div className="menu">
        {menuData.map(menu => (
          <div
            key={menu.key}
            className={`menu-item ${currentMenuItem === menu.key ? 'active' : ''}`}
            onClick={() => handleMenuItemClick(menu.key)}
          >
            {menu.title}
          </div>
        ))}
      </div>
      <div className="content">
        {contentData.map(content => (
          <div key={content.key} id={content.key} ref={contentRef} className="content-item">
            <h1>{content.title}</h1>
            <p>{content.content}</p>
          </div>
        ))}
      </div>
    </div>
  )
}

export default AnchorComponent;

css

css 复制代码
.anchor-component-container {
    display: flex;
  }
  
  .menu {
    flex: 0 0 200px;
    height: 100vh;
    overflow-y: auto;
    position: sticky;
    top: 0;
  }
  
  .menu-item {
    padding: 10px;
    cursor: pointer;
  }
  
  .menu-item.active {
    background-color: paleturquoise;
  }
  
  .content {
    flex: 1;
  }
  
  .content-item {
    padding: 50px;
    height: 600px;
    border: 1px solid blue;
  }

效果如下:

屏幕录制2023-08-08 17.32.08

相关推荐
阿成学长_Cain18 分钟前
Linux telinit 命令详解:运行级别切换|关机重启|系统维护一站式掌握
linux·运维·前端·网络
Patrick_Wilson32 分钟前
最佳实践是有保质期的:从一次 CDN external 白屏事故说起
前端·性能优化·前端工程化
YHHLAI1 小时前
[特殊字符] Agent 智能体开发实战 · 第一课:Tool Use —— 让大模型自动干活
前端·人工智能
nuIl1 小时前
我把 5 个编码 Agent 塞进了一个 npm 包
前端·agent·claude
L-影1 小时前
FastAPI 静态文件:Web 页面的“固定展柜”与“加速引擎”
前端·fastapi
陪我去看海1 小时前
受够了 AI 写完代码留下一堆端口?我做了一个端口管理App!
前端·macos·vibecoding
Xuepoo1 小时前
我抛弃了 DOM,但保留了无障碍访问
前端
李明卫杭州1 小时前
Vue2 vs Vue3 的 h 函数:渲染函数完整迁移指南
前端
月光刺眼1 小时前
用LangChain 打造第一个 Agent:LLM 大脑与 Tool 手脚的完整闭环
javascript·人工智能·全栈
星栈1 小时前
LiveView 的认证系统:从登录到权限,我一开始以为有 `current_user` 就算完事了
前端·前端框架·elixir