问题描述
在我的react项目中,我希望导航向下滚动时隐藏,向上滚动时显示。我正在使用窗口事件监听器进行滚动,从而触发了toggleNavigation
函数以及CSS transform: translateY
和transition
。无论我使用鼠标滚轮,键盘箭头还是拖动滚动条,导航都在上下滚动时闪烁。
(运行toogleNavigation
时还有其他一些条件,但它们不相关。)
导航组件
import React,{ useEffect,useState } from "react";
export default Navigation = () => {
const [prevScrollpos,setPrevScrollpos] = useState(window.pageYOffset);
const [translateY,setTranslateY] = useState("0");
const toggleNavigation = (prevScrollpos) => {
const currentScrollpos = window.pageYOffset;
if (currentScrollpos > prevScrollpos + 50) {
//navigationHtml.style.top = '-116px'
setTranslateY("-116px");
setPrevScrollpos(currentScrollpos);
} else if (currentScrollpos + 50 < prevScrollpos) {
//navigationHtml.style.top = '0'
setTranslateY("0");
setPrevScrollpos(currentScrollpos);
}
};
useEffect(() => {
window.addEventListener(
"scroll",toggleNavigation.bind(this,prevScrollpos)
);
return () => {
window.removeEventListener("scroll",toggleNavigation);
};
},[prevScrollpos]);
return (
<nav style={{ transform: `translateY(${translateY})` }}>
<ul>
<li>random text 1</li>
<li>random text 2</li>
</ul>
</nav>
);
};
CSS
nav {
background-color: orangered;
position: fixed;
height: 120px;
width: 100%;
transition: transform 0.5s;
}
ul {
list-style-type: none;
display: flex;
justify-content: center;
}
li {
margin: 20px;
}
工作代码和框: https://codesandbox.io/s/jolly-haze-flfjj?file=/src/Navigation.js
到目前为止,我已经尝试了并且没有起作用:
反应中:将隐藏/显示导航的时间限制从50更改为100或1
在CSS中:
- 将
translateY
更改为translate3D
- 在其他元素上使用
translate3D(0,0)
- 在导航和其他元素上设置
backface-visibility: hidden
在我使用更改top
而不是translateY
之前,尽管动画没有闪烁,但动画并没有流畅流畅。
我在Chrome,Edge,Firefox和Opera中对其进行了测试,所有这些问题均存在。
任何帮助将不胜感激。
解决方法
所以我自己解决了这个问题。问题是我在React中的糟糕设计。我不应该使用useState
钩来跟踪滚动,因为它会导致重新设计组件。经过几次滚动后,这种缓慢的行为是显而易见的。在性能更高的PC(更多的组件翻新)上花费了更多的滚动时间,才显得不那么理想。
现在,我使用简单的let
变量跟踪滚动,并且导航组件仅在显示或隐藏时才重新渲染。
代码和框: https://codesandbox.io/s/condescending-minsky-00xi0?file=/src/Navigation.js
,很高兴听到它运行正常。 但是您可以走得更远,因为滚动事件被触发了很多次,所以对滚动事件进行计算从来就不是一个好主意。如果您的网页上还有其他滚动事件,它可能会变慢/变慢。
您还可以使用自定义的挂钩将节流功能添加到滚动事件中
以下是受限制的滚动事件Hook(与SRR兼容)的示例:
import { useEffect,useState } from "react"
// You can use Lodash throttle function or aby other one,your own,whatever...
import { throttle } from "lodash"
function useDocumentScrollThrottled(callback) {
const [,setScrollPosition] = useState(0)
let previousScrollTop = 0
function handleDocumentScroll() {
const { scrollTop: currentScrollTop } =
typeof window === "undefined" || !window.document
? 0
: document.documentElement || document.body
setScrollPosition(previousPosition => {
previousScrollTop = previousPosition
return currentScrollTop
})
callback({ previousScrollTop,currentScrollTop })
}
// => 250: 4fps / triggered times per seconds (250 is in milliseconds) || 16: 60fps (because 1000/60 = ~16)
const handleDocumentScrollThrottled = throttle(handleDocumentScroll,250)
useEffect(() => {
window.addEventListener("scroll",handleDocumentScrollThrottled)
return () =>
window.removeEventListener("scroll",handleDocumentScrollThrottled)
},[handleDocumentScrollThrottled])
}
export default useDocumentScrollThrottled
然后在您的组件中(以您的Nav组件为例)您可以通过以下方式使用它:
export default function Nav() {
const MINIMUM_SCROLL = 30;
const TIMEOUT_DELAY = 50;
const [shouldHideNav,setShouldHideNav] = useState(false) ;
useDocumentScrollThrottled(({ previousScrollTop,currentScrollTop }) => {
// Do whatever you want like by example
const isScrolledDown = previousScrollTop < currentScrollTop;
const isMinimumScrolled = currentScrollTop > MINIMUM_SCROLL;
setTimeout(() => {
setShouldHideNav(isScrolledDown && isMinimumScrolled)
},TIMEOUT_DELAY)
})
return (<nav className={`nav ${shouldHideNav ? 'nav--hidden' : ''}`}>Nav content</nav>)
}