What's the difference between onScroll
and onScrollCapture
in React?
Not sure if this is a DOM or pure JS topic since I can't find any resources about onScrollCapture
.
const node = (
<div onScrollCapture={handleScrollCapture} />
);
CodePudding user response:
onScrollCapture
in React is the equivalent of doing:
addEventListener('scroll', listener, { capture: true })
or
addEventListener('scroll', listener, true)
in vanilla JS.
Both say: If the event fires on a child element, run my handler first (parent) before running their handlers. For example, when you click the button in the demo below, the browser will first run the event handler for the parent div before it runs the handler for the button itself:
const parent = document.querySelector('.parent');
const button = parent.querySelector('button');
parent.addEventListener('click', () => console.log('parent'), true);
button.addEventListener('click', (e) => {
console.log('button');
});
.parent {
width: 100px;
height: 100px;
border: solid 1px black;
display: flex;
align-items: center;
justify-content: center;
}
button {
background: white;
}
<div >
<button>
Click me
</button>
</div>
Traditionally, when registering an event listener, you would just do addEventListener('event', listener)
without the third argument (or onEvent={listener}
in React). If the event target is a child nested inside the current element (like the button in the example above), this means the event will:
- Fire on the target first (the button), and then
- Fire on this element (the parent div) as it bubbles up the tree (assuming it bubbles at all).
Which is the reverse order of what you'd get with onEventCapture
and its vanilla-JS equivalent.
More generally, all events go through three phases:
- Capturing, where you start at the root of the DOM tree and trace the path down to find the element that actually triggered the event (the target).
- Targeting, where the event fires on the target itself once it's reached.
- Bubbling, where the event travels back up to the root of the DOM and fires on every parent element along the way. (If and only if the event bubbles.)
For parent elements, events only fire in either the bubbling or capturing phase, not both, and when they fire is determined by:
- Whether the event bubbles at all (some events don't).
- Whether you supplied the third argument to
addEventListener
.
Importantly, the scroll
event does not bubble. So in the example below, only the child div's messages are logged to the console when you scroll it:
const parent = document.querySelector('.parent');
const child = parent.querySelector(':scope > *');
parent.addEventListener('scroll', () => console.log('parent scrolled'));
child.addEventListener('scroll', () => console.log('child scrolled'));
body {
display: flex;
justify-content: center;
align-items: center;
}
div {
border: solid 1px black;
}
.parent {
height: 250px;
overflow-y: scroll;
max-width: 200px;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.parent>* {
height: 200px;
overflow-y: auto;
}
<div >
<div tabindex="0" role="region">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin mattis augue quis turpis vestibulum volutpat a a sem. Maecenas ornare ac felis sed volutpat. Curabitur dapibus ante nec ante congue, et consectetur nunc tempor. Sed gravida posuere sollicitudin.
Etiam feugiat a nulla vel consequat. Vestibulum vel massa faucibus, rutrum felis quis, mollis orci. Vivamus vestibulum ligula non orci cursus tincidunt. Proin hendrerit tincidunt finibus. Donec vitae aliquet sem. Nunc vel purus non nunc consectetur
luctus in vel mauris. Donec varius quam risus, non lacinia orci rutrum in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nunc turpis, cursus sodales vulputate eu, eleifend fermentum est. Aliquam sollicitudin congue justo vitae ullamcorper.
Proin lectus mi, faucibus vitae pharetra id, fringilla sit amet arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut ut ipsum nulla. Proin in aliquet purus, quis consectetur risus. Aliquam erat volutpat. Sed auctor tristique interdum.
Morbi ac purus eget leo interdum gravida at ac nunc. Donec commodo vitae quam pulvinar bibendum. Duis nunc enim, scelerisque vel maximus quis, semper eu ipsum. Nam eu mollis ante.
</div>
</div>
This means that if you want to add an event listener to a parent element and detect scrolls on a child element, then you need to use scroll capturing, like this:
const parent = document.querySelector('.parent');
const child = parent.querySelector(':scope > *');
parent.addEventListener('scroll', () => console.log('parent scrolled'), true);
child.addEventListener('scroll', () => console.log('child scrolled'));
body {
display: flex;
justify-content: center;
align-items: center;
}
div {
border: solid 1px black;
}
.parent {
height: 250px;
overflow-y: scroll;
max-width: 200px;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.parent>* {
height: 200px;
overflow-y: auto;
}
<div >
<div tabindex="0" role="region">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin mattis augue quis turpis vestibulum volutpat a a sem. Maecenas ornare ac felis sed volutpat. Curabitur dapibus ante nec ante congue, et consectetur nunc tempor. Sed gravida posuere sollicitudin.
Etiam feugiat a nulla vel consequat. Vestibulum vel massa faucibus, rutrum felis quis, mollis orci. Vivamus vestibulum ligula non orci cursus tincidunt. Proin hendrerit tincidunt finibus. Donec vitae aliquet sem. Nunc vel purus non nunc consectetur
luctus in vel mauris. Donec varius quam risus, non lacinia orci rutrum in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nunc turpis, cursus sodales vulputate eu, eleifend fermentum est. Aliquam sollicitudin congue justo vitae ullamcorper.
Proin lectus mi, faucibus vitae pharetra id, fringilla sit amet arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut ut ipsum nulla. Proin in aliquet purus, quis consectetur risus. Aliquam erat volutpat. Sed auctor tristique interdum.
Morbi ac purus eget leo interdum gravida at ac nunc. Donec commodo vitae quam pulvinar bibendum. Duis nunc enim, scelerisque vel maximus quis, semper eu ipsum. Nam eu mollis ante.
</div>
</div>