准备工作
首先,我们要理解scrollY、innerHeight、offsetTop、offsetHeight这些概念。
这些属性都与 Web 开发中的滚动条位置、元素高度等相关。
核心思想是:计算每个菜单项对应的内容区域的位置范围,并将当前滚动条位置和内容区域位置进行比较,以找到当前所在的菜单项。
-
scrollY
:表示文档在垂直方向上已经滚动的像素值,即滚动条距离页面顶部的距离,通常用于判断滚动条的位置。例如,window.scrollY
表示当前页面向下滚动的像素值 -
innerHeight
:表示浏览器窗口的视口高度,即浏览器可见区域的高度,不包括滚动条和工具栏。例如,window.innerHeight
表示当前浏览器窗口的视口高度。 -
offsetTop
:表示当前元素的上边缘相对于其 offsetParent 元素的上边缘的距离,即元素距离其父元素顶部的距离。例如,element.offsetTop
表示当前元素距离其 offsetParent 元素顶部的距离。 -
offsetHeight
:表示当前元素的高度,包括元素的高度、上下内边距和边框。例如,element.offsetHeight
表示当前元素的高度,包括元素的上下内边距和边框。
需要注意的是,scrollY
和 innerHeight
属性是 window 对象的属性,而 offsetTop
和 offsetHeight
属性是元素对象的属性,可以通过 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