You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Moving the mouse: mouseover/out, mouseenter/leave
2
2
3
-
Let's dive into more details about events that happen when mouse moves between elements.
3
+
Let's dive into more details about events that happen when the mouse moves between elements.
4
4
5
-
## Mouseover/mouseout, relatedTarget
5
+
## Events mouseover/mouseout, relatedTarget
6
6
7
7
The `mouseover` event occurs when a mouse pointer comes over an element, and `mouseout` -- when it leaves.
8
8
9
9

10
10
11
-
These events are special, because they have a `relatedTarget`.
12
-
13
-
This property complements `target`. When a mouse leaves one element for another, one of them becomes `target`, and the other one `relatedTarget`.
11
+
These events are special, because they have property `relatedTarget`. This property complements `target`. When a mouse leaves one element for another, one of them becomes `target`, and the other one - `relatedTarget`.
14
12
15
13
For `mouseover`:
16
14
@@ -19,13 +17,13 @@ For `mouseover`:
19
17
20
18
For `mouseout` the reverse:
21
19
22
-
-`event.target` -- is the element that mouse left.
20
+
-`event.target` -- is the element that the mouse left.
23
21
-`event.relatedTarget` -- is the new under-the-pointer element, that mouse left for (`target` -> `relatedTarget`).
24
22
25
23
```online
26
-
In the example below each face feature is an element. When you move the mouse, you can see mouse events in the text area.
24
+
In the example below each face and its features are separate elements. When you move the mouse, you can see mouse events in the text area.
27
25
28
-
Each event has the information about where the element came and where it came from.
26
+
Each event has the information about both `target` and `relatedTarget`:
29
27
30
28
[codetabs src="mouseoverout" height=280]
31
29
```
@@ -38,103 +36,128 @@ That's normal and just means that the mouse came not from another element, but f
38
36
We should keep that possibility in mind when using `event.relatedTarget` in our code. If we access `event.relatedTarget.tagName`, then there will be an error.
39
37
```
40
38
41
-
## Events frequency
39
+
## Skipping elements
42
40
43
41
The `mousemove` event triggers when the mouse moves. But that doesn't mean that every pixel leads to an event.
44
42
45
43
The browser checks the mouse position from time to time. And if it notices changes then triggers the events.
46
44
47
-
That means that if the visitor is moving the mouse very fast then DOM-elements may be skipped:
45
+
That means that if the visitor is moving the mouse very fast then some DOM-elements may be skipped:
48
46
49
47

50
48
51
49
If the mouse moves very fast from `#FROM` to `#TO` elements as painted above, then intermediate `<div>` (or some of them) may be skipped. The `mouseout` event may trigger on `#FROM` and then immediately `mouseover` on `#TO`.
52
50
53
-
In practice that's helpful, because if there may be many intermediate elements. We don't really want to process in and out of each one.
51
+
That's good for performance, because if there may be many intermediate elements. We don't really want to process in and out of each one.
54
52
55
-
On the other hand, we should keep in mind that we can't assume that the mouse slowly moves from one event to another. No, it can "jump".
53
+
On the other hand, we should keep in mind that the mouse pointer doesn't "visit" all elements along the way. It can "jump".
56
54
57
-
In particular it's possible that the cursor jumps right inside the middle of the page from out of the window. And `relatedTarget=null`, because it came from "nowhere":
55
+
In particular, it's possible that the pointer jumps right inside the middle of the page from out of the window. In that case `relatedTarget` is `null`, because it came from "nowhere":
58
56
59
57

60
58
61
-
<div style="display:none">
62
-
In case of a fast move, intermediate elements may trigger no events. But if the mouse enters the element (`mouseover`), when we're guaranteed to have `mouseout` when it leaves it.
63
-
</div>
64
-
65
59
```online
66
-
Check it out "live" on a teststand below.
60
+
You can check it out "live" on a teststand below.
67
61
68
-
The HTML is two nested `<div>` elements. If you move the mouse fast over them, then there may be no events at all, or maybe only the red div triggers events, or maybe the green one.
62
+
Its HTML has two nested elements: the `<div id="child">` is inside the `<div id="parent">`. If you move the mouse fast over them, then maybe only the child div triggers events, or maybe the parent one, or maybe there will be no events at all.
69
63
70
-
Also try to move the pointer over the red `div`, and then move it out quickly down through the green one. If the movement is fast enough then the parent element is ignored.
64
+
Also move the pointer into the child `div`, and then move it out quickly down through the parent one. If the movement is fast enough, then the parent element is ignored. The mouse will cross the parent element without noticing it.
71
65
72
66
[codetabs height=360 src="mouseoverout-fast"]
73
67
```
74
68
75
-
## "Extra" mouseout when leaving for a child
69
+
```smart header="If `mouseover` triggered, there must be `mouseout`"
70
+
In case of fast mouse movements, intermediate elements may be ignores, but one thing we know for sure: elements can be only skipped as a whole.
71
+
72
+
If the pointer "officially" entered an element with `mouseover`, then upon leaving it we always get `mouseout`.
73
+
```
76
74
77
-
Imagine -- a mouse pointer entered an element. The `mouseover` triggered. Then the cursor goes into a child element. The interesting fact is that `mouseout` triggers in that case. The cursor is still in the element, but we have a `mouseout` from it!
75
+
## Mouseout when leaving for a child
76
+
77
+
An important feature of `mouseout` -- it triggers, when the pointer moves from an element to its descendant.
78
+
79
+
Visually, the pointer is still on the element, but we get `mouseout`!
78
80
79
81

80
82
81
-
That seems strange, but can be easily explained.
83
+
That looks strange, but can be easily explained.
82
84
83
-
**According to the browser logic, the mouse cursor may be only over a *single* element at any time -- the most nested one (and top by z-index).**
85
+
**According to the browser logic, the mouse cursor may be only over a *single* element at any time -- the most nested one and top by z-index.**
84
86
85
-
So if it goes to another element (even a descendant), then it leaves the previous one. That simple.
87
+
So if it goes to another element (even a descendant), then it leaves the previous one.
86
88
87
-
There's a funny consequence that we can see on the example below.
89
+
Please note an important detail.
88
90
89
-
The red `<div>` is nested inside the blue one. The blue `<div>` has `mouseover/out` handlers that log all events in the textarea below.
91
+
The `mouseover` event on a descendant bubbles up. So, if the parent element has such handler, it triggers.
90
92
91
-
Try entering the blue element and then moving the mouse on the red one -- and watch the events:
93
+

92
94
93
-
[codetabs height=360 src="mouseoverout-child"]
95
+
So, when we move from a parent element to a child, then two handlers trigger on the parent element: `mouseout` and `mouseover`:
96
+
97
+
```js
98
+
parent.onmouseout = function(event) {
99
+
/* event.target: parent element */
100
+
};
101
+
parent.onmouseover = function(event) {
102
+
/* event.target: child element */
103
+
};
104
+
```
105
+
106
+
If the code inside the handlers doesn't look at `target`, then it might think that the mouse left the `parent` element, and then came back over it. But it's not the case! The mouse never left, it just moved to the child element.
94
107
95
-
1. On entering the blue one -- we get `mouseover [target: blue]`.
96
-
2. Then after moving from the blue to the red one -- we get `mouseout [target: blue]` (left the parent).
97
-
3. ...And immediately `mouseover [target: red]`.
108
+
```online
109
+
In the example below the `<div id="child">` is inside the `<div id="parent">`. There are handlers on the parent that listen for `mouseover/out` events and output their details.
110
+
111
+
If you move the mouse from the parent to the child, you see two events: `mouseout [target: parent]` (left the parent) and `mouseover [target: child]` (came to the child, bubbled).
98
112
99
-
So, for a handler that does not take `target` into account, it looks like we left the parent in `mouseout` in `(2)` and returned back to it by `mouseover` in `(3)`.
113
+
[codetabs height=360 src="mouseoverout-child"]
114
+
```
100
115
101
-
If we perform some actions on entering/leaving the element, then we'll get a lot of extra "false" runs. For simple stuff that may be unnoticeable. For complex things that may bring unwanted side-effects.
116
+
If there's some action upon leaving the element, e.g. animation runs, then such interpretation may bring unwanted sideeffects.
102
117
103
-
We can fix it by using `mouseenter/mouseleave` events instead.
118
+
To avoid it, we can check `relatedTarget` and, if the mouse is still inside the element, then ignore such event.
119
+
120
+
Alternatively we can use other events: `mouseenter` и `mouseleave`, that we'll be covering now, as they don't have such problems.
104
121
105
122
## Events mouseenter and mouseleave
106
123
107
-
Events `mouseenter/mouseleave` are like `mouseover/mouseout`. They also trigger when the mouse pointer enters/leaves the element.
124
+
Events `mouseenter/mouseleave` are like `mouseover/mouseout`. They trigger when the mouse pointer enters/leaves the element.
108
125
109
-
But there are two differences:
126
+
But there are two important differences:
110
127
111
128
1. Transitions inside the element are not counted.
112
129
2. Events `mouseenter/mouseleave` do not bubble.
113
130
114
-
These events are intuitively very clear.
131
+
These events are extremely simple.
132
+
133
+
When the pointer enters an element -- `mouseenter` triggers. The exact location of the pointer inside the element or its descendants doesn't matter.
115
134
116
-
When the pointer enters an element -- the `mouseenter` triggers, and then doesn't matter where it goes while inside the element. The `mouseleave`event only triggers when the cursor leaves it.
135
+
When the pointer leaves an element -- `mouseleave` triggers.
117
136
118
-
If we make the same example, but put `mouseenter/mouseleave` on the blue `<div>`, and do the same -- we can see that events trigger only on entering and leaving the blue `<div>`. No extra events when going to the red one and back. Children are ignored.
137
+
```online
138
+
This example is similar to the one above, but now the top element has `mouseenter/mouseleave` instead of `mouseover/mouseout`.
139
+
140
+
As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignores
119
141
120
142
[codetabs height=340 src="mouseleave"]
143
+
```
121
144
122
145
## Event delegation
123
146
124
147
Events `mouseenter/leave` are very simple and easy to use. But they do not bubble. So we can't use event delegation with them.
125
148
126
149
Imagine we want to handle mouse enter/leave for table cells. And there are hundreds of cells.
127
150
128
-
The natural solution would be -- to set the handler on `<table>` and process events there. But `mouseenter/leave` don't bubble. So if such event happens on `<td>`, then only a handler on that `<td>`can catch it.
151
+
The natural solution would be -- to set the handler on `<table>` and process events there. But `mouseenter/leave` don't bubble. So if such event happens on `<td>`, then only a handler on that `<td>`is able to catch it.
129
152
130
-
Handlers for `mouseenter/leave` on `<table>` only trigger on entering/leaving the whole table. It's impossible to get any information about transitions inside it.
153
+
Handlers for `mouseenter/leave` on `<table>` only trigger when the pointer enters/leaves the table as a whole. It's impossible to get any information about transitions inside it.
131
154
132
-
Not a problem -- let's use `mouseover/mouseout`.
155
+
So, let's use `mouseover/mouseout`.
133
156
134
-
A simple handler may look like this:
157
+
Let's start with handlers that highlight the element under mouse:
These handlers work when going from any element to any inside the table.
178
+
In our case we'd like to handle transitions between table cells `<td>`: entering a cell and leaving it. Other transitions, such as inside the cell or outside of any cells, don't interest us. Let's filter them out.
154
179
155
-
But we'd like to handle only transitions in and out of `<td>` as a whole. And highlight the cells as a whole. We don't want to handle transitions that happen between the children of `<td>`.
180
+
Here's what we can do:
156
181
157
-
One of solutions:
158
-
159
-
- Remember the currently highlighted `<td>` in a variable.
182
+
- Remember the currently highlighted `<td>` in a variable, let's call it `currentElem`.
160
183
- On `mouseover` -- ignore the event if we're still inside the current `<td>`.
161
184
- On `mouseout` -- ignore if we didn't leave the current `<td>`.
162
185
163
-
That filters out "extra" events when we are moving between the children of `<td>`.
186
+
Here's an example of code that accounts for all possible situations:
164
187
165
-
```offline
166
-
The details are in the [full example](sandbox:mouseenter-mouseleave-delegation-2).
Try to move the cursor in and out of table cells and inside them. Fast or slow -- doesn't matter. Only `<td>` as a whole is highlighted unlike the example before.
195
+
Try to move the cursor in and out of table cells and inside them. Fast or slow -- doesn't matter. Only `<td>` as a whole is highlighted, unlike the example before.
175
196
```
176
197
177
-
178
198
## Summary
179
199
180
200
We covered events `mouseover`, `mouseout`, `mousemove`, `mouseenter` and `mouseleave`.
181
201
182
-
Things that are good to note:
202
+
These things are good to note:
183
203
184
-
- A fast mouse move can make `mouseover, mousemove, mouseout` to skip intermediate elements.
185
-
- Events `mouseover/out` and `mouseenter/leave` have an additional target: `relatedTarget`. That's the element that we are coming from/to, complementary to `target`.
186
-
- Events `mouseover/out` trigger even when we go from the parent element to a child element. They assume that the mouse can be only over one element at one time -- the deepest one.
187
-
- Events `mouseenter/leave` do not bubble and do not trigger when the mouse goes to a child element. They only track whether the mouse comes inside and outside the element as a whole.
204
+
- A fast mouse move may skip intermediate elements.
205
+
- Events `mouseover/out` and `mouseenter/leave` have an additional property: `relatedTarget`. That's the element that we are coming from/to, complementary to `target`.
206
+
- Events `mouseover/out` trigger even when we go from the parent element to a child element. The browser assumes that the mouse can be only over one element at one time -- the deepest one.
207
+
- Events `mouseenter/leave` do not bubble and do not trigger when the mouse goes to a child element. They only track whether the mouse comes inside and outside the element as a whole. So, they are simpler than `mouseover/out`, but we can't implement delegation using them.
Copy file name to clipboardExpand all lines: 2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js
+18-9
Original file line number
Diff line number
Diff line change
@@ -2,37 +2,46 @@
2
2
letcurrentElem=null;
3
3
4
4
table.onmouseover=function(event){
5
-
if(currentElem){
6
-
// before entering a new element, the mouse always leaves the previous one
7
-
// if we didn't leave <td> yet, then we're still inside it, so can ignore the event
8
-
return;
9
-
}
5
+
// before entering a new element, the mouse always leaves the previous one
6
+
// if currentElem is set, we didn't leave the previous <td>,
7
+
// that's a mouseover inside it, ignore the event
8
+
if(currentElem)return;
10
9
11
10
lettarget=event.target.closest('td');
12
-
if(!target||!table.contains(target))return;
13
11
14
-
// yeah we're inside <td> now
12
+
// we moved not into a <td> - ignore
13
+
if(!target)return;
14
+
15
+
// moved into <td>, but outside of our table (possible in case of nested tables)
16
+
// ignore
17
+
if(!table.contains(target))return;
18
+
19
+
// hooray! we entered a new <td>
15
20
currentElem=target;
16
21
target.style.background='pink';
17
22
};
18
23
19
24
20
25
table.onmouseout=function(event){
21
26
// if we're outside of any <td> now, then ignore the event
27
+
// that's probably a move inside the table, but out of <td>,
28
+
// e.g. from <tr> to another <tr>
22
29
if(!currentElem)return;
23
30
24
-
// we're leaving the element -- where to? Maybe to a child element?
31
+
// we're leaving the element -- where to? Maybe to a descendant?
0 commit comments