我正在尝试编写基本的挂钩,以在滚动时获取currentScroll,lastScroll,scrollSpeed。
function useDocScroll() {
const isClient = typeof window === "object"
function getScroll() {
return isClient
? window.pageYOffset || document.documentElement.scrollTop
: undefined
}
const [docScroll, setDocScroll] = useState(getScroll)
const [lastScroll, setLastScroll] = useState(null)
const [scrollSpeed, setScrollSpeed] = useState(Math.abs(docScroll - lastScroll))
useEffect(() => {
if (!isClient) {
return false
}
function handleScroll() {
setDocScroll(getScroll())
setLastScroll(getScroll()) // <-- why is this working?
// setLastScroll(docScroll) // <-- why is this not working?
setScrollSpeed(Math.abs(docScroll - lastScroll)) // <-- why is this not working?
}
window.addEventListener("scroll", handleScroll)
}, [])
return [docScroll, lastScroll, scrollSpeed]
}
看来当我执行
setLastScroll(getScroll())
时,它很好地保存了上一个滚动值。但是我不明白,因为在触发
handleScroll()
时,getScroll()
的值不应该保持不变吗?我不明白为什么setDocScroll(getScroll())
和setLastScroll(getScroll())
具有不同的值。另外,我以为我可以做
setLastScroll(docScroll)
,意思是“用当前的docScroll值设置lastScroll值”,但是当docScroll值更改时,它只会打印“ 0”。为什么是这样?我想更好地理解。
+)我无法获取由
scrollSpeed
和docScroll
计算的lastScroll
,但是我不知道如何获取这些值。最佳答案
我认为您的代码为何无法正常工作是由于以下两个原因:
在docScroll
之后直接使用setDocScroll
无效,因为setState是异步任务。无法保证在执行下一条语句之前已更新docScroll
您得到0
的原因是某些特定元素内可能发生了滚动(可能)。由于document.documentElement
指向html
元素,因此内部没有滚动。这样您收到0
解:
您不需要多个useState
。由于滚动事件发出的频率太高,所以我认为使用useReducer
减少渲染数量是个好主意。重要的是要了解在根级别或某个元素内部发生滚动的位置。
对于以下解决方案,我建议:
如果滚动发生在根级别(html元素),则无需将元素传递给useDocScroll
。如果在特定元素内发生滚动,则需要传递元素引用。
const initState = {
current: 0,
last: 0,
speed: 0,
};
function reducer(state, action) {
switch (action.type) {
case "update":
return {
last: state.current,
current: action.payload,
speed: Math.abs(action.payload - state.current) || 0,
};
default:
return state;
}
}
const isClient = () => typeof window === "object";
function useDocScroll(element = document.documentElement) {
const [{ current, last, speed }, dispatch] = useReducer(reducer, initState);
function getScroll() {
return isClient() ? element.scrollTop : 0;
}
function handleScroll() {
dispatch({ type: "update", payload: getScroll() });
}
useEffect(() => {
if (!isClient()) {
return false;
}
element.addEventListener("scroll", handleScroll);
return () => element.removeEventListener("scroll", handleScroll);
}, []);
return [current, last, speed];
}
例:
如果窗口内发生滚动
const {current, last, speed} = useDocScroll()
如果滚动发生在特定元素中
const {current, last, speed} = useDocScroll(document.getElementById("main"))