JS Scroll event efficiency - javascript

Hi everyone I would like to know when my user has scroll bellow 220px. I've created a event who trigger correctly my useState when the user scrolled below 220px
window.addEventListener("scroll", function () {
if (this.window.scrollY > 200 && isScroll === false) {
console.log("bon");
setIsScroll(true);
} else if (this.window.scrollY < 200 && isScroll === true) {
console.log("fini");
setIsScroll(false);
}
});
my question is: Is their a better way to do it because when I scrool only a little I've got a lot of instruction. And Will it be a problem on my application efficiency?

MAJOR performance issue due to the event listener not being wrapped in a useEffect with a remove event listener as the return.
You're just piling up event listeners every time the component rerenders. Which is why you're seeing tens of thousands of console.logs after just a few scrolls.
const [isScroll, setIsScroll] = useState(false);
useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 200 && isScroll === false) {
console.log("bon");
setIsScroll(true);
} else if (window.scrollY < 200 && isScroll === true) {
console.log("fini");
setIsScroll(false);
}
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [isScroll]);
Above solution solves this problem. Note the isScroll parameter being passed to the useEffect dependency array in order to update the handleScroll function with the current state.

Related

React Material: progressBar with mouse listener

I've faced with problem using React and React Material-UI components. What I need:
1) User clicks button in my component - I should add mousemove listener to the page and show ProgressBar.
2) User moves mouse - I count events, and update my ProgressBar.
3) When count of events is 50, I remove mousemove listener and hide ProgressBar.
I tried to do this with React useEffect, useState Hooks, but it does not remove listener. I don't understand, why.
Here is my code:
const [completed, setCompleted] = React.useState(0);
const [keyGenState, setKeyGenState] = React.useState(0);
const updateMousePosition = ev => {
console.log("UMP");
setCompleted(old => old + 1);
/*I tried to check completed value here, but it always is 0 - maybe, React re-renders component on setState..
And I decided to take useEffect hook (see below)*/
};
useEffect(() => {
console.log(completed); /*Just to understand, what happens) */
if (completed === 49) {
return () => {
/*When completed value is 50, this log message appears, but mouse listener still works! */
console.log("Finish!");
document.removeEventListener("mousemove", updateMousePosition);
setKeyGenState(2);
}
}
}, [completed]);
function handleChange(e) {
switch (e.currentTarget.id) {
/*startKeyGen - it is ID of my Button. When user clicks it, I show ProgressBar and add mousemove listener*/
case "startKeyGen" : {
setKeyGenState(1);
document.addEventListener("mousemove", updateMousePosition);
break;}
}
}
/*Other logics. And finally, JSX code for my ProgressBar from Material-UI*/
<LinearProgress hidden={keyGenState !== 1 } variant="determinate" value={completed} style={{marginTop: 10}} />
It looks really strange: why React ignores removeEventListener.
Please, explain, where is my mistake.
UPD: Thanks a lot! I used useCallback hook, in this manner:
const updateMousePosition = React.useCallback(
(ev) => {
//console.log("Calback");
console.log(ev.clientX);
setCompleted(old => old + 1);
},
[],
);
useEffect(() => {
//console.log(completed); /*Just to understand, what happens) */
if (completed === 49) {
return () => {
/*When completed value is 50, this log message appears, but mouse listener still works! */
console.log("Finish!");
document.removeEventListener("mousemove", updateMousePosition);
setKeyGenState(2);
}
}
});
But I still don't understand completely.. So, when I used useCallback with empty dependencies array, this function (updateMousePosition), will be unchanged during all "life" of my component? And in useEffect I remove mouseListener. It is magic for me: why does useEffect ignore removing without useCallback?
Try to use React.useCallback for updateMousePosition. Every change in your component creates new function (reference). So React.useEffect remove last updateMousePosition but doesn't remove added in handleChange.

How could I see if my scroll reached a ref ? React

I'm building a navbar who changes its background color if the user has scrolled until an ad.
Before, I used the method "window.scrollY" and change the color if its number is over 700.
Unfortunatly this method is bad to be responsive with differents screens.
In my navabr when I click on a title, the website automaticaly scroll until the part.
I used references to do it and I would like to know if you know a way to check if the scroll reached a reference in the top of the screen (to replace the bad color system).
Like ref.current.isReached ? return a boolean.
With that I could change my css in a better way.
I hope I was clear, my english is very bad.
This is my classe with the ref system:
const Home = ({ section }: Props) => {
const sectionRef1: any = useRef(React.createRef());
const sectionRef2: any = useRef(React.createRef());
const scroll = (ref: any) => {
ref.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
};
function scrollToSection(section: number) {
if (section === 1) {
scroll(sectionRef1);
}
else if (section === 2) {
scroll(sectionRef2);
}
else if (section === 3) {
//TODO: active button
}
}
useEffect(() => {
scrollToSection(section);
}, [section]);
return (
<div>
<div ref={sectionRef1} />
<Carrousel></Carrousel>
<div ref={sectionRef2} className="margin_top_portrait" />
<Portrait></Portrait>
</div>
);
}
export default Home;
Thanks in advance
useEffect(() => {
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, []);
In onScroll function you can find out whatever your want about your element position with ref.current.getBoundingClientRect()
Example of checking if element is in viewport

Calling a method when scroll starts

I have a react component that uses scroll events. When the scroll event is called it basically runs the handler as expected. However I need to be able to call a method once when the scroll events begin to fire. I understand the idea of a debounce method which would fire when the scroll stops, but I need to find a way to fire once when scrolling begins. (For sure NO jQuery can be used).
componentDidMount() {
window.addEventListener('scroll', this.onScroll);
this.setState({
// sets some state which is compared in a shouldComponentUpdate
});
}
componentWillUnmount() {
window.removeEventListener('scroll', this.onScroll);
}
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
The handler seen below runs some code:
onScroll() {
this.scrollTop = document.documentElement.scrollTop;
this.update();
}
But I need a function that runs once:
scrollStart() {
}
I would love to say I have tried something but unfortunately I have no ideas. Any assist would be greatly appreciated.
There is no real scrolling state in the browser; the scroll event happens, and then it's over.
You could e.g. create a new timeout each time the user scrolls and set your own scrolling state to false if the user hasn't scrolled until the timeout function is run.
Example
class App extends React.Component {
timeout = null;
state = { isScrolling: false };
componentDidMount() {
window.addEventListener("scroll", this.onScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.onScroll);
}
onScroll = () => {
clearTimeout(this.timeout);
const { isScrolling } = this.state;
if (!isScrolling) {
this.setState({ isScrolling: true });
}
this.timeout = setTimeout(() => {
this.setState({ isScrolling: false });
}, 200);
};
render() {
return (
<div
style={{
width: 200,
height: 1000,
background: this.state.isScrolling ? "green" : "red"
}}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Do you want the scrollStart() function to only fire once, the very first time a user scrolls? If do, this could be easily accomplished with a scrollStarted variable. if (scrollStarted == false) { scrollStarted = true; scrollStart(); }.
I can imagine a similar scenario if you want the function to fire when a user starts scrolling from the top (it can fire again if the user returns to the top). Just replace (scrollStarted == false) with scrollTop == 0.
There is no scrollstart event in javascript, however you can register pointer events on the parent element and scroll events on the target element.
Then, for example, in the pointerdown callback reset a variable that gets set when scrolling starts.
If you want you can even dispatch a custom "scrollstart" event on the target when the scroll event is triggered and var scrolling is not set.
For document.body you can listen for pointer ( or touch or mouse ) events on window.
For this you could define a static variable and when the scroll starts, put true in the variable and enter a while that keeps checking if the scrool continues. Using this logic, you may be able to do something.

Adding a className on scroll?

How can I add a className when the page scrolls? I have ready many other articles and answers to this (may be a duplicate) but none have helped me understand what is wrong with my code below.
If the code is not the issue I believe that it stems from a perspective wrapper around the app that may disallow the registration of scroll. How can I add the event listener to register scroll on id=container
constructor(props) {
super(props)
this.state = {
isStuck: true,
}
this.handleHeaderStuck = this.handleHeaderStuck.bind(this)
}
componentDidMount () {
window.addEventListener('scroll', this.handleHeaderStuck);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleHeaderStuck);
}
handleHeaderStuck() {
if (window.scrollY === 0 && this.state.isStuck === true) {
this.setState({isStuck: false});
}
else if (window.scrollY !== 0 && this.state.isStuck !== true) {
this.setState({isStuck: true});
}
}
render() {
return (
<main className={this.state.isStuck ? 'header-stuck' : ''}>
...
</main>
This screenshot reassures me that the issue is with the registering of onScroll listener
Be sure your component have enough height for scroll. Your code works.
Add some height to main and check it.
main {
height: 2000px;
}
https://jsfiddle.net/69z2wepo/156204/
You code has an issue, onScroll is attached a listener function handleScroll whereas the function is handleHeaderStuck in your case. Change the listener to execute the correct function.
componentDidMount () {
window.addEventListener('scroll', this.handleHeaderStuck);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleHeaderStuck);
}

event listener on window doesn't remove [duplicate]

This question already has answers here:
JavaScript: remove event listener
(10 answers)
Closed 7 years ago.
I need to remove an event listener set on window, but it doesn't work, the listener keeps firing on scroll. I've tried to set the listener with and without lodash's throttle but it doesn't make any difference. Here's my code:
setupListener() {
window.addEventListener('resize', _.throttle(this.handler.bind(this), 750));
window.addEventListener('scroll', _.throttle(this.handler.bind(this), 750));
}
removeListener() {
window.removeEventListener('resize', _.throttle(this.handler.bind(this), 750));
window.removeEventListener('scroll', _.throttle(this.handler.bind(this), 750));
window.addEventListener('load', this.handler.bind(this), false);
}
static isElementInViewport (el) {
let rect = el.getBoundingClientRect();
return (
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
handler() {
if (this.options.url === undefined) {throw new Error('no url specified');}
if (InfiniteScroll.isElementInViewport(this.elementToWatch)) {
this.removeListener();
this[this.options.transport]();
}
}
I've also tried to promisify the removal:
handler() {
if (this.options.url === undefined) {throw new Error('no url specified');}
if (InfiniteScroll.isElementInViewport(this.elementToWatch)) {
Promise.resolve(this.removeListener())
.then(val => {
this[this.options.transport]();
});
}
}
which didn't make a difference either.
Later in the code, I want to reassign the listeners:
handleResponse(data) {
console.log('handleResponse' + data);
Promise.resolve(this.addElementsToDOM(data))
.delay(1000)
.then(() => {
this.page++;
this.elementToWatch = document.getElementById(this.element).rows[document.getElementById(this.element).rows.length - this.options.loadTiming];
//this.setupListener();
});
}
I've logged every step of the and am unable to find the reason for it. Can someone please assist?
Sidenote: Is there a better way of handling the scroll event than to remove and add the listeners all the time?
The thing is: when you add an event listener, the browser saves as its 'key' the function reference that you're passing as a parameter. So, when you want to remove it, you must send that reference.
There are two ways to solve your problem. The first is to create named functions:
setupListener() {
this.listener = function() {
_.throttle(this.handler.bind(this), 750);
}.bind(this);
window.addEventListener('resize', this.listener);
window.addEventListener('scroll', this.listener);
}
removeListener() {
window.removeEventListener('resize', this.listener);
window.removeEventListener('scroll', this.listener);
window.addEventListener('load', this.handler.bind(this), false);
}
The other, is by overriding the Window's addEventListener method, but I don't suggest you to do that.

Categories