Question:
I have a container with a bound (wheel) event listener containing some headings and paragraphs just for demo purposes. When I use the mouse wheel to scroll, the scroll event is occurring on one of the inner elements rather than on the container having the bound event.
Why is an event firing on a child element of a container rather than on the bound element itself?
Background:
I am playing around with angular on StackBlitz and wanted to implement an event listener on a <div> container to handle the scroll event:
<div class="inner no-scroll" (wheel)="blockScroll($event)">
In my app.component.ts I created the event handler to be called:
blockScroll(e) {
console.log(e);
if (e.target !== e.currentTarget) {
return;
}
let eO = e; // e.originalEvent;
var delta = eO.deltaY || -eO.detail;
e.target.scrollTop += delta * 30;
e.stopPropagation();
e.preventDefault();
}
In Firefox Developer Tools I see that the event is properly bound to the <div>:
Now when I use the mouse wheel over the container I see that in nearly all cases the event target is not the bound <div> but rather a <h3> or <p> element inside of the container. I wonder why the wrong element is triggering the scroll event?
This is the expected behavior.
target - element that triggered event (element directly under the mouse pointer at the beginning of the scroll)
currentTarget - element that listens to event (the div element in this case)
So if you were to point at the top of the container and begin scrolling, both target and currentTarget would point to the same div element. But once it's scrolled past the top of the container, the elements directly beneath the mouse pointer would be the <p> or <h3> elements.
Related
I'm having trouble understanding how to detect "mouse3" / the mouse scroll button down event using react's onMouseDown event.
I'm using onMouseDown on some element:
<Menu.Item
icon={<DashboardOutlined style={{ fontSize: '21px' }} />}
onClick={onClickDash}
onMouseDown={handleEvent}
>
Dashboard
</Menu.Item>
And then the handleEvent:
const handleEvent = (event) => {
if (event.type === 'mousedown') {
console.log('MOUSE DOWN', event);
} else {
console.log('MOUSE UP', event);
}
};
Problem is, I can't find a way of detecting the mouse scroll button down specifically. How can I detect that? The official mozilla docs don't provide an example either.
Disclaimer - At time of writing I was only targeting Chrome browser.
When a user scrolls a DOM scroll event is fired, an event which is built into ever browser by default. React has it’s own method, onScroll, which can be called on any component when the scroll event is fired. Using this onScroll method we can call a function as a result of a user scrolling.
Example;
<SomeComponent onScroll={someMeothod} />
As with any DOM event, an event object is created with properties that can provide useful information about the event and the element said event is related too. A scroll event is fired for every pixel a user scrolls. In this case, we are only concerned with identifying when the user has scrolled to the end of the parent element containing our content.
Calculating when user has scrolled to end of a container
Add an onScroll method to element containing content which calls a function on the components class:
<div className="content-container" onScroll={this.handleScroll}>
// Your content
</div>
Then create the handleScroll function to handle the scroll event:
class MyComponent extends React.Component {
handleScroll = e => {
let element = e.target
if (element.scrollHeight - element.scrollTop === element.clientHeight) {
// do something at end of scroll
}
}
render() {
return (
<div className="content-container" onScroll={this.handleScroll}>
// Your content
</div>
)
}
}
Let’s break down what is happening in the handleScroll method to make things a bit clearer…
e - this corresponds to the event itself. It is an object that is created by the browser with properties related to the scroll event we are working with.
let element = e.target - this allows us to find the element which dispatched the event using (e.target) and assign it to the variable which we can use in the rest of the code.
Now we (our code) knows which element is being scrolled and we have assigned it to a variable in our methods scope, we can access the properties of that element given by the browser and calculate if the user has scrolled to the end.
element.scrollHeight - this is the height in pixels of the elements content, including content not visible on the screen due to css overflow.
element.scrollTop - the height in pixels that an element's content is scrolled vertically.
element.clientHeight - the height in pixels of the scrollable part of the element.
Using the above properties, we can calculate if the user has scrolled to the bottom of the element by comparing the negative sum of the scrollHeight and scrollTop to the clientHeight. If they are are the same, the user has scrolled to the bottom of the element. By wrapping this in an if statement we can therefore ensure that our function within the if statement’s scope is only run when the user has scrolled to the end of the div and our if condition is met.
I have implemented a TabbarController where in the user can swipe through different tabs. I have achieved this by adding 'touchstart', 'touchmove' & 'touchend' events. All the tabs have a horizontally scrollable element which when scrolled leads to invoking of 'touchmove' on parent which leads to parent and child scrolling at same time. Is it possible to restrict the scroll to child / restrict the touch event from being passed on from child to parent?
I have tried using 'overflow:hidden' on parent div and also tries using 'prevendefault()' on touch events.
I finally wrote a solution to cater to my problem. I iterate through the DOM objects to check for the scrollable area of the child elements when the Event callback propagates back to the Parent element where the listeners were added.
The Touch Event is not processed for parent if the current touched child has scrollable content.
var element:HTMLElement = event.target;
var cancel:boolean = false;
while (element && element.parentElement){
if (element.scrollWidth > element.clientWidth){
cancel = true;
break;
}
element = element.parentElement;
}
Structure:
.parent (has if/else to toggle on click) -> .child (has nothing)
<div class="parent">Parent
<div class="child">Child</div>
</div>
The parent element is styled to hide overflowing content and toggle its height on click. When the user clicks, the parent element will expand to show the child element. I want users to be able to click on the child element without the parent element toggling back to its original size and hiding the child element. I want the toggle to only happen on the parent.
I realize the child element is still contained within the parent element's clickable area, but is there a way to exclude it?
Solution 1: Compare target with currentTarget:
$("#parentEle").click( function(e) {
if(e.target == e.currentTarget) {
alert('parent ele clicked');
} else {
//you could exclude this else block to have it do nothing within this listener
alert('child ele clicked');
}
});
Fiddle
e.target will be the element that started the event.
e.currentTarget will be where it currently is (bubbling up) which will be parentEle in this click event as that's what this is listening for.
If they are the same, you know the click was directly on the parent.
Solution 2: Stop the propagation before the event hits the parentEle:
The other option is to prevent the event from bubbling up in the first place if there is a click on a child element. That can be done like this:
$("#parentEle").click( function(e) {
alert('parent ele clicked');
});
$("#parentEle").children().click( function(e) {
//this prevent the event from bubbling to any event higher than the direct children
e.stopPropagation();
});
Fiddle
The main difference between the two is that the first solution will just ignore the event in this listener and allow it to keep bubbling up. This may be necessary if you have a parent of this parentEle that needs to get the event.
The second solution stops any click events from bubbling past parentEle's direct children. So if there was a click event on a parent of parentEle, they would never see these events either.
Use event.stopPropagation();:
$('#a').add('#b').click(fun1);
function handler(event) {
event.stopPropagation();
// now do your stuff
}
When entering a DOM element, mouseover event will happen. Upon moving the mouse around the current element, no event happens, as mouseover is for entering.
However, this rule is not obeyed for child nodes. If moving the mouse over child nodes, the mouseover event will be triggered again and again, though no new event, as we are still in the original parent.
See this example. If we move the mouse over the parent (actually on its textNode), nothing new happens, but if we go to the child element (still on the parent), it will trigger the mouseover event again and again. In fact it will fire the mouse event every time mouse enters an elements (even inside the original parent element).
How we can make the mouseover only once for moving all over the parent (the original element in the addEventListener)? In the given example, I mean to avoid firing the event upon moving the mouse on the child element.
What you really need is the mouseenter event, which does not bubble (unlike mouseover). From MDN:
The synchronous mouseenter DOM event is dispatched when a mouse or
another pointing device enters the physical space given to the element
or one of its descendants.
Similar to mouseover , it differs in that it doesn't bubble and that
it isn't sent when the pointer is moved from one of its descendants'
physical space to its own physical space.
Unfortunately, it's not widely supported. One solution is to use jQuery (see .mouseenter()), which polyfills that event in all the browsers it supports. Another option is to check the element that triggered the event:
document.getElementById("parent").addEventListener('mouseover', function(e) {
if (e.target.id === "parent") { //Only execute following on parent mouseover
document.getElementById("time").innerHTML = new Date;
this.childNodes[1].style.display="block";
}
}, false);
Or see here for what looks at first glance to be a pretty good shim.
This works for me in chrome and ff
document.getElementById("parent").addEventListener('mouseover', function(event) {
var e = event.fromElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
return;
}
document.getElementById("time").innerHTML = new Date();
}, true);
Fiddle Demo
Reference: Mouse Events
in your example child element is not firing any event it is your parent element on which when you mouseover it runs your script and display result in your "time" div.
I have an element with some child elements. When the mouse leaves the parent element I want to hide the parent and it's children. Problem I'm having is that when I hover over any of the children, the mouseout event is being fired. What's the best way to prevent this? I really only want the event to fire when the mouse is not within the parent or any of it's children.
The event is bubbling up from the child to the parent (where it is being caught)
You should catch the event on the children by adding a listener and making the propagation stop there.
This code will stop the event from bubbling up to the parents handler
function onMouseLeave(e)
{
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}
question: The mouse off event is fired by the parent, when it should not. Mouseing over the child should not trigger a mouse off from the parent. How can we stop this?
You only need a mouseout handler attached to the parent element. But...
You need to check that the parent is actually the target of the mouseout event, as opposed to the event bubbling up from one of the children. Check event.target (W3C) and event.srcElement (IE).
You also need to check that the element that the mouse will be entering is not a descendant of the parent. Check event.relatedTarget (W3C) and event.toElement (IE).
From http://api.jquery.com/mouseover/:
mouseover fires when the pointer moves into the child element as well, while mouseenter fires only when the pointer moves into the bound element.
and the same goes for mouseout vs mouseleave