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);
}
Related
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.
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
Following is my handleScroll function in which I am trying to add red class if it scroll down to a certain limit else apply blue. However this is only going inside the else statement and also console.log(e.target.scrollTop); its consoling as undefined. Let me know what I am doing wrong here.
Code -
handleScroll(e) {
console.log(e.target.scrollTop);
if (window.screenX > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Codesandbox - https://codesandbox.io/s/silly-feynman-m6hp1
I would highly recommend adding an extra check to your condition. When you scroll a single-time, you update your component-state multiple times after a certain range (100), which is unnecessary. You only need to update it once.
It will keep updating because you meet the condition inside handleScroll, even though the background has already changed. The sheer amount of updates can cause your app to crash.
Try something like this it will update your component-state as expected and only when necessary: https://codesandbox.io/s/white-architecture-jepyc
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
navBckgroundColor: `blue`,
altered: false
};
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
//use arrow function instead so that its "this" keyword points to the component's execution context. Otherwise, "this" will point to the global window object where you cannot use this.setState.
handleScroll = e => {
if (window.pageYOffset > 100 && !this.state.altered) {
this.setState({
navBckgroundColor: `red`,
altered: true
});
} else if(window.pageYOffset < 100 && this.state.altered) {
this.setState({
navBckgroundColor: `blue`,
altered: false
});
}
};
render() {
return (
<div className="App">
<Navbar bckGroundColor={this.state.navBckgroundColor} />
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
Use window.scrollY instead of window.screenY and also bind the handleScroll method.
handleScroll = (e) => {
if (window.scrollY > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Working demo
Please use
handleScroll = e => {
console.log(e.target.scrollTop);
if (window.scrollY > 100) {
this.setState({
navBckgroundColor: `red`
});
} else {
this.setState({
navBckgroundColor: `blue`
});
}
}
Please see the workable code on :
https://codesandbox.io/s/friendly-swirles-bwl06
also your window.screenX will always output the same value, and thus no change to the colors.
I have changed that in the code as well
Please read code first.
After css processing, it seems like memo application's single memo paper.
The goal of the component is to print a 1 when clicked(in real, the goal is to hadding redux store's state).
When i click outside of div component, it works very well. ( it printed '1' )
but when i clicked inner div component(title, date,content), onClick event also proceed ( it printed '')
how can i prevent non-valued print?
My code :
class container extends Component {
handleState = (event) => {
console.log(event.target.id)
}
render(){
return(
<div onClick={handleState} id={value}>
<div>title</div>
<div>date</div>
<div>content</div>
</div>
)
}
}
container.defaultprops = {
value: 1
}
thanks.
You can use currentTarget:
handleState = (event) => {
console.log(event.currentTarget.id)
}
About difference between target and currentTarget:
https://stackoverflow.com/a/10086501/5709697
You can use currentTarget to check if it's the target since you bound the handler to the parent e.g.
handleState = (event) = > {
if (event.target == event.currentTarget) {
console.log(event.target.id)
}
}
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.