用鼠标滚轮对水平滚动做出反应
react horizontal scroll to section with mouse wheel
我有一个简单的反应项目:
有水平菜单和内容。
通过单击菜单项 - 滚动到当前部分。
滚动内容时,切换活动菜单项。
导航:
<ul
className="nav list-unstyled d-flex flex-nowrap fixed-top"
ref={scrollNavRefs}
onScroll={handleScroll}
>
{list.map((item, i) => (
<li className="nav-item" key={i}>
<a
href={`#s-${i}`}
className={`nav-link text-nowrap ${
active === i ? "text-danger" : ""
}`}
onClick={scrollTo(i)}
>
{item}
</a>
</li>
))}
</ul>
栏目:
<ul className="mb-100 list-unstyled">
{list.map((item, i) => (
<li id={`s-${i}`} ref={scrollRefs.current[i]} className="py-100 px-3">
<h3>{item}</h3>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi,
dicta.
</p>
</li>
))}
</ul>
>>>:
const scrollRefs = useRef([]);
const scrollNavRefs = useRef();
const [active, setActive] = useState(0);
const list = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"];
scrollRefs.current = [...Array(list.length).keys()].map(
(_, i) => scrollRefs.current[i] ?? createRef()
);
const scrollTo = (index) => () => {
scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
setActive(index);
};
const scrollHandler = () => {
const scrollRefsElements = scrollRefs.current;
scrollRefsElements.forEach((el, i) => {
const rect = el.current.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible = elemTop >= 0 && elemBottom <= window.innerHeight;
if (isVisible) {
setActive(i);
}
});
};
const onWheel = (e) => {
if (e.deltaY === 0) return;
e.preventDefault();
const scrollNavRefsElement = scrollNavRefs.current;
const scrollNavRefsElementLeft = scrollNavRefs.current.scrollLeft;
scrollNavRefsElement.scrollTo({
left: scrollNavRefsElementLeft + e.deltaY,
behavior: "smooth"
});
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
useEffect(() => {
scrollNavRefs.current.addEventListener("wheel", onWheel, true);
return () => {
scrollNavRefs.current.removeEventListener("wheel", onWheel, true);
};
}, []);
问题:如何link菜单和内容,以便在滚动消费内容时,当活动的class菜单项发生变化时,菜单滚动以便活动菜单项可见?类推example.
P.S:
- 查看 320px 扩展
- 垂直滚动内容:
- 如果第 1 部分 - 然后激活菜单项 1,如果第 2 部分 - 然后激活菜单项 2,...
- 如果活动菜单项不可见 - 则水平滚动菜单以便菜单项可见
- 水平滚动:
- 如果菜单项 1 处于活动状态 - 垂直滚动到第 1 部分,如果菜单项 2 处于活动状态 - 垂直滚动到部分 2,...类似 example 或仅通过单击。
是的。您可以使用 IntersectionObserver。像:
https://codesandbox.io/s/react-scroll-nav-to-forked-ohci4c
我已经修改了你的沙箱 Working Demo。
进行了以下更改:
- 为每个菜单项创建
navRefs
。并且对 navRefs
做了与 scrollRefs
. 相同的事情
// called it when user scrolls vertically, or clicks on a menu item
navRefs.current[i].current.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
- 更改逻辑以查找当前聚焦的内容。
const isVisible =
elemTop < window.innerHeight / 2 && elemBottom > window.innerHeight / 2;
简而言之,如果内容位于视口的水平中心,则 select 它。如果多个内容同时出现在视口中,这可以避免 select 编辑多个内容。
- 使用标志来避免在用户单击菜单项时执行
scrollHandler
。还避免了导航栏上的水平滚动事件。
- 在 CSS 中隐藏导航栏上的水平滚动条。并在 selected 菜单项中添加了红色下划线。
可供参考的代码:
import "./styles.css";
import React, { createRef, useRef, useState, useEffect } from "react";
var scrolling = false;
export default function App() {
const scrollRefs = useRef([]);
const navRefs = useRef([]);
const [active, setActive] = useState(0);
const list = [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9",
"Item 10"
];
scrollRefs.current = [...Array(list.length).keys()].map(
(_, i) => scrollRefs.current[i] ?? createRef()
);
navRefs.current = [...Array(list.length).keys()].map(
(_, i) => navRefs.current[i] ?? createRef()
);
const scrollTo = (index) => {
console.log("setting scrolling" + scrolling);
scrolling = true;
scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
setActive(index);
setTimeout(() => {
scrolling = false;
navRefs.current[index].current.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
}, 1000);
};
const scrollHandler = (e) => {
// handle scroll only on body
// we don't want to handle horizontal scroll on nav bar
if (e.target !== document) return;
if (scrolling === true) return;
const scrollRefsElements = scrollRefs.current;
scrollRefsElements.forEach((el, i) => {
const rect = el.current.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible =
elemTop < window.innerHeight / 2 && elemBottom > window.innerHeight / 2;
if (isVisible) {
navRefs.current[i].current.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
setActive(i);
}
});
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
return (
<div className="container">
<ul className="myMenu nav list-unstyled d-flex flex-nowrap fixed-top">
{list.map((item, i) => (
<li className="nav-item " key={i} ref={navRefs.current[i]}>
<a
href={`#s-${i}`}
className={`nav-link text-nowrap ${
active === i ? "text-danger" : ""
}`}
onClick={(e) => {
scrollTo(i);
}}
>
{item}
</a>
</li>
))}
</ul>
<ul className="mb-100 list-unstyled">
{list.map((item, i) => (
<li id={`s-${i}`} ref={scrollRefs.current[i]} className="py-100 px-3">
<h3>{item}</h3>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi,
dicta.
</p>
</li>
))}
</ul>
</div>
);
}
我有一个简单的反应项目:
有水平菜单和内容。
通过单击菜单项 - 滚动到当前部分。
滚动内容时,切换活动菜单项。
导航:
<ul
className="nav list-unstyled d-flex flex-nowrap fixed-top"
ref={scrollNavRefs}
onScroll={handleScroll}
>
{list.map((item, i) => (
<li className="nav-item" key={i}>
<a
href={`#s-${i}`}
className={`nav-link text-nowrap ${
active === i ? "text-danger" : ""
}`}
onClick={scrollTo(i)}
>
{item}
</a>
</li>
))}
</ul>
栏目:
<ul className="mb-100 list-unstyled">
{list.map((item, i) => (
<li id={`s-${i}`} ref={scrollRefs.current[i]} className="py-100 px-3">
<h3>{item}</h3>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi,
dicta.
</p>
</li>
))}
</ul>
>>>:
const scrollRefs = useRef([]);
const scrollNavRefs = useRef();
const [active, setActive] = useState(0);
const list = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"];
scrollRefs.current = [...Array(list.length).keys()].map(
(_, i) => scrollRefs.current[i] ?? createRef()
);
const scrollTo = (index) => () => {
scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
setActive(index);
};
const scrollHandler = () => {
const scrollRefsElements = scrollRefs.current;
scrollRefsElements.forEach((el, i) => {
const rect = el.current.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible = elemTop >= 0 && elemBottom <= window.innerHeight;
if (isVisible) {
setActive(i);
}
});
};
const onWheel = (e) => {
if (e.deltaY === 0) return;
e.preventDefault();
const scrollNavRefsElement = scrollNavRefs.current;
const scrollNavRefsElementLeft = scrollNavRefs.current.scrollLeft;
scrollNavRefsElement.scrollTo({
left: scrollNavRefsElementLeft + e.deltaY,
behavior: "smooth"
});
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
useEffect(() => {
scrollNavRefs.current.addEventListener("wheel", onWheel, true);
return () => {
scrollNavRefs.current.removeEventListener("wheel", onWheel, true);
};
}, []);
问题:如何link菜单和内容,以便在滚动消费内容时,当活动的class菜单项发生变化时,菜单滚动以便活动菜单项可见?类推example.
P.S:
- 查看 320px 扩展
- 垂直滚动内容:
- 如果第 1 部分 - 然后激活菜单项 1,如果第 2 部分 - 然后激活菜单项 2,...
- 如果活动菜单项不可见 - 则水平滚动菜单以便菜单项可见
- 水平滚动:
- 如果菜单项 1 处于活动状态 - 垂直滚动到第 1 部分,如果菜单项 2 处于活动状态 - 垂直滚动到部分 2,...类似 example 或仅通过单击。
是的。您可以使用 IntersectionObserver。像: https://codesandbox.io/s/react-scroll-nav-to-forked-ohci4c
我已经修改了你的沙箱 Working Demo。
进行了以下更改:
- 为每个菜单项创建
navRefs
。并且对navRefs
做了与scrollRefs
. 相同的事情
// called it when user scrolls vertically, or clicks on a menu item
navRefs.current[i].current.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
- 更改逻辑以查找当前聚焦的内容。
const isVisible =
elemTop < window.innerHeight / 2 && elemBottom > window.innerHeight / 2;
简而言之,如果内容位于视口的水平中心,则 select 它。如果多个内容同时出现在视口中,这可以避免 select 编辑多个内容。
- 使用标志来避免在用户单击菜单项时执行
scrollHandler
。还避免了导航栏上的水平滚动事件。 - 在 CSS 中隐藏导航栏上的水平滚动条。并在 selected 菜单项中添加了红色下划线。
可供参考的代码:
import "./styles.css";
import React, { createRef, useRef, useState, useEffect } from "react";
var scrolling = false;
export default function App() {
const scrollRefs = useRef([]);
const navRefs = useRef([]);
const [active, setActive] = useState(0);
const list = [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9",
"Item 10"
];
scrollRefs.current = [...Array(list.length).keys()].map(
(_, i) => scrollRefs.current[i] ?? createRef()
);
navRefs.current = [...Array(list.length).keys()].map(
(_, i) => navRefs.current[i] ?? createRef()
);
const scrollTo = (index) => {
console.log("setting scrolling" + scrolling);
scrolling = true;
scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
setActive(index);
setTimeout(() => {
scrolling = false;
navRefs.current[index].current.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
}, 1000);
};
const scrollHandler = (e) => {
// handle scroll only on body
// we don't want to handle horizontal scroll on nav bar
if (e.target !== document) return;
if (scrolling === true) return;
const scrollRefsElements = scrollRefs.current;
scrollRefsElements.forEach((el, i) => {
const rect = el.current.getBoundingClientRect();
const elemTop = rect.top;
const elemBottom = rect.bottom;
const isVisible =
elemTop < window.innerHeight / 2 && elemBottom > window.innerHeight / 2;
if (isVisible) {
navRefs.current[i].current.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "center"
});
setActive(i);
}
});
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
return (
<div className="container">
<ul className="myMenu nav list-unstyled d-flex flex-nowrap fixed-top">
{list.map((item, i) => (
<li className="nav-item " key={i} ref={navRefs.current[i]}>
<a
href={`#s-${i}`}
className={`nav-link text-nowrap ${
active === i ? "text-danger" : ""
}`}
onClick={(e) => {
scrollTo(i);
}}
>
{item}
</a>
</li>
))}
</ul>
<ul className="mb-100 list-unstyled">
{list.map((item, i) => (
<li id={`s-${i}`} ref={scrollRefs.current[i]} className="py-100 px-3">
<h3>{item}</h3>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi,
dicta.
</p>
</li>
))}
</ul>
</div>
);
}