如何解决我有一个计数器,当页面滚动到某个元素进行反应时,它只能被触发一次?
我在页面的某个部分有一个计数器,当滚动到达该元素时,它只需要触发一次。我可以在 jQuery 中看到各种解决方案,但我正在寻找 react 或纯 javascript 方面的答案。
我使用状态和反应钩子实现了它,但即使我保留状态并使用条件语句,我也会看到一些不稳定的触发不止一次。我不确定我哪里出错了。
这里是react类组件(由于使用useEffect时不断警告,我从函数切换到类)
更新:我找到了如下可行的解决方案。如果有其他更好的方法可以做到这一点,请告诉我。
import React,{ useState,useCallback,useEffect } from "react";
export default function DataCounter({ count,title }) {
const [number,setNumber] = useState(0);
const [trigger,setTrigger] = useState(false);
const increment = useCallback(() => {
const counter = (length = 2000) => {
setNumber(0);
let n = count;
let start = Date.now();
let end = start + length;
let interval = length / n;
const sInt = setInterval(() => {
let time = Date.now();
if (time < end) {
let count = Math.floor((time - start) / interval);
setNumber(count);
} else {
setNumber(n);
clearInterval(sInt);
}
},interval);
};
counter();
},[count]);
document.addEventListener("scroll",async () => {
const element = await document.getElementById("data");
const elementPosition = await element.getBoundingClientRect().top;
if (window.pageYOffset > elementPosition) setTrigger(true);
});
useEffect(() => {
if (trigger) {
increment();
}
},[trigger,increment]);
return (
<div className="counter">
<div className="counter-number">{number}</div>
<p className="counter-title">{title}</p>
</div>
);
}
初始版本:
import React from "react";
export default class Counter extends React.Component {
state = {
number: 0,loaded: false
};
increment(n,length = 2000){
this.setState({ number: 0 });
let start = Date.now();
let end = start + length;
let interval = length / n;
const sInt = setInterval(() => {
let time = Date.now();
if (time < end) {
let count = Math.floor((time - start) / interval);
this.setState({ number: count });
} else {
this.setState({ number: n });
clearInterval(sInt);
}
},interval);
};
async handleScroll() {
const element = await document.getElementById("data");
const elementPosition = await element.getBoundingClientRect().bottom;
if (window.pageYOffset > elementPosition) {
if(!this.state.loaded) {
this.increment(100);
this.setState({ loaded: true });
}
return;
}
}
componentDidMount() {
window.addEventListener("scroll",this.handleScroll);
}
render() {
return (
<div className="counter">
<div className="counter-number">{this.state.number}</div>
<p className="counter-title">{this.props.title}</p>
</div>
);
}
}
解决方法
首先,您以错误的方式声明了类方法。由于您使用 ES6 方式声明状态(您没有使用构造函数),因此您也必须以 ES6 方式声明您的类方法。比如,
increment = (n,length = 2000) => {
// so you can access *this* variable in this scope
...
}
handleScroll
方法也是如此。如果你想像你在代码中所做的那样声明一个辅助方法:
increment(n,length = 2000){
...
}
然后需要声明一个构造函数,并在构造函数中绑定this
。可以找到引用here。
其次,完全没有必要将 handleScroll
作为异步函数。此方法中没有返回任何承诺。
最后但并非最不重要的一点,请始终记住删除 componentWillUnmount
中的滚动侦听器,这样当用户在路由之间切换或组件再次挂载时,您将不会有一堆侦听器侦听 windows 滚动事件.
这是a working example on codesandbox。
,我发现你的代码有问题
您正在函数组件的主体内注册“滚动”事件侦听器。这就是滚动事件被多次触发的原因。
因为函数组件的每一次渲染都会给DOM增加一个额外的滚动事件
注册和清理任何 DOM 事件的正确方法是在像这样的“useEffect”反应钩子内
const handleScrollEvent = async () => {
const element = await document.getElementById("data");
const elementPosition = await element.getBoundingClientRect().top;
if (window.pageYOffset > elementPosition) setTrigger(true);
}
const registerEvent = () => {
document.addEventListener("scroll",handleScrollEvent);
}
const unRegisterEvent = () => {
document.removeEventListener("scroll",handleScrollEvent);
}
useEffect(() => {
if (trigger) {
increment();
}
//Register the event inside useEffect after the component mounts
registerEvent();
//Cleanup the registered event once the component is unmounted from
//the DOM
return unRegisterEvent;
},[trigger,increment]);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。