事件mouseover / mouseout, relatedTarget
一、当鼠标指针移到某个元素上时,mouseover事件就会发生,而当鼠标离开该元素时,mouseout事件就会发生。

二、mouseover、 mouseout具有relatedTarget属性。此属性是对target的补充。
1、当鼠标从一个元素离开并去往另一个元素时,其中一个元素就变成了target,另一个就变成了relatedTarget
(1)mouseover
① event.target:是鼠标移过的那个元素
② event.relatedTarget:是鼠标来自的那个元素(relatedTarget -> target)
(2)mouseout
① event.target:鼠标离开的元素
② event.relatedTarget:鼠标移动到的,当前指针位置下的元素(target -> relatedTarget)
2、relatedTarget可以为null。这意味着鼠标不是来自另一个元素,而是来自窗口之外,或者它离开了窗口。
跳过元素
一、当鼠标移动时,就会触发mousemove事件。但这并不意味着每个像素都会导致一个事件。
1、浏览器会一直检查鼠标的位置。如果发生了变化,就会触发事件。
2、这意味着,如果访问者非常快地移动鼠标,那么某些DOM元素就可能被跳过。
【示例1】如果鼠标从上图所示的#FORM快速移动到#T0元素,则中间的
(或其中的一些)元素可能回被跳过。mouseout事件可能会在#FORM上被触发,然后立即在#TO上触发mouseover。

3、这对性能很有好处,因为可能会有很多中间元素。我们并不真的想要处理每一个移入和离开的过程。
二、鼠标指针并不会“访问”所有元素。它可以“跳过”一些元素。
1、鼠标指针可能会从窗口外跳到页面的中间。在这种情况下,relatedTarget为null。

三、在鼠标快速移动的情况下,中间元素可能会被忽略,但是,如果鼠标进入了一个元素(生成了mouseover事件),那么一旦它离开,我们就会得到mouseout
当移动到一个子元素时mouseout
一、mouseout的一个重要功能:当鼠标指针从元素移动到其后代时触发。
【示例1】如果我们在#parent上,然后将鼠标指针更深入地移入#child,但是在#parent上会得到mouseout

1、根据浏览器的逻辑,鼠标指针随时可能位于单个元素上,嵌套最多的那个元素(z-index最大的那个)。如果它转到另一个元素(甚至是一个后代),那么它将离开前一个元素。
2、后代的mouseover事件会冒泡。因此,如果#parent具有mouseover处理程序,它将被触发
【示例1】鼠标指针从#parent元素移动到#child时,会在父元素上触发两个处理程序: mouseout 和mouseover

parent.onmouseout = function(event) {
/* event.target = parent element */
}
parent.onmouseover = function(event) {
/* event.target = child elment (bubbled) */
}
(1)如果我们不检查处理程序中的event.target,那么似乎鼠标指针离开了#parent元素,然后立即回到了它上面。但事实并非如此,鼠标指针仍然位于父元素上,只是更深入地移入了子元素。
【示例1】如果离开父元素时有一些行为(action),例如一个动画在parent.onmouseout中运行,当鼠标指针深入#parent时,我们并不希望发生这种行为。为了避免它,我们可以在处理程序中检查relatedTarget,如果鼠标指针仍然在元素内,则忽略此类事件。
二、mouseenter,mouseleave,它们没有此类问题。
事件mouseenter 和mouseleave
一、事件mouseenter / mouseleave 类似于mouseover / mouseout。它们在鼠标指针进入 / 离开元素时触发。
1、区别
(1)元素内部与后代之间的转换不会产生影响。
(2)事件mouseenter / mouseleave不会冒泡。
二、当鼠标指针进入一个元素时,会触发mouseenter。而鼠标指针在元素或其后代中的确切位置无关紧要。
当鼠标指针离开该元素时,事件mouseleave才会触发。
【示例1】鼠标指针进入或移出#parent,会触发mouseenter, mouseleave,当鼠标指针进入child并返回时,不会触发#parent的这两个事件。在后代之间的移动会被忽略。
<div id="parent" onmouseenter="mouselog(event)" onmouseleave="mouselog(event)">parent
<div id="child">child</div>
</div>
<script>
function mouselog(event) {
let d = new Date();
text.value += `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()} | ${event.type} [target: ${event.target.id}]\n`.replace(/(:|^)(\d\D)/, '$10$2');
text.scrollTop = text.scrollHeight;
}
</script>
事件委托
一、事件mouseenter / mouseleave不会冒泡。因此,我们不能使用它们来进行事件委托。
【示例1】假设要处理表格的单元格的鼠标进入 / 离开。并且这里有数百个单元格。
1、解决方案:通常解决方案是
中设置处理程序,并在那里处理事件。但mouseenter / leave不会冒泡。因此,如果类似的事件发生在
上,那么只有 | 上的处理程序才能捕获到它。上的mouseenter /leave的处理程序仅在鼠标指针进入/离开整个表格时才会触发。无法获取有关其内部移动的任何信息。 因此,我们使用mouseover / mouseout 2、高亮显示鼠标指针下的元素// 高亮显示鼠标指针下的元素
table.onmouseover = function(event) {
let target = event.target;
target.style.background = 'pink';
};
table.onmouseout = function(event) {
let target = event.target;
target.style.background = '';
};
3、我们想要处理表格的单元格
之间的移动:进入一个单元格并离开它。所以我们需要过滤其他移动,如在单元格内部或在所有单元格的外部。 (1)思路 ① 在变量中记住当前被高亮显示的 | ,让我们称它为currentElem ② mouseover:如果我们仍然在当前的 | 中,则忽略该事件。 ③ mouseout:如果没有离开当前的 | ,则忽略。 (2)实现代码
<table id="table">
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td class="nw"><strong>Northwest</strong>
<br>Metal
<br>Silver
<br>Elders
</td>
<td class="n"><strong>North</strong>
<br>Water
<br>Blue
<br>Change
</td>
<td class="ne"><strong>Northeast</strong>
<br>Earth
<br>Yellow
<br>Direction
</td>
</tr>
<tr>
<td class="w"><strong>West</strong>
<br>Metal
<br>Gold
<br>Youth
</td>
<td class="c"><strong>Center</strong>
<br>All
<br>Purple
<br>Harmony
</td>
<td class="e"><strong>East</strong>
<br>Wood
<br>Blue
<br>Future
</td>
</tr>
<tr>
<td class="sw"><strong>Southwest</strong>
<br>Earth
<br>Brown
<br>Tranquility
</td>
<td class="s"><strong>South</strong>
<br>Fire
<br>Orange
<br>Fame
</td>
<td class="se"><strong>Southeast</strong>
<br>Wood
<br>Green
<br>Romance
</td>
</tr>
</table>
// 现在位于鼠标下方的 <td>(如果有)
let currentElem = null;
table.onmouseover = function(event) {
// 在进入一个新的元素前,鼠标总是会先离开前一个元素
// 如果设置了 currentElem,那么我们就没有鼠标所悬停在的前一个 <td>,
// 忽略此事件
if (currentElem) return;
let target = event.target.closest('td');
// 我们移动到的不是一个 <td> —— 忽略
if (!target) return;
// 现在移动到了 <td> 上,但在处于了我们表格的外部(可能因为是嵌套的表格)
// 忽略
if (!table.contains(target)) return;
// 给力!我们进入了一个新的 <td>
currentElem = target;
onEnter(currentElem);
};
table.onmouseout = function(event) {
// 如果我们现在处于所有 <td> 的外部,则忽略此事件
// 这可能是一个表格内的移动,但是在 <td> 外,
// 例如从一个 <tr> 到另一个 <tr>
if (!currentElem) return;
// 我们将要离开这个元素 —— 去哪儿?可能是去一个后代?
let relatedTarget = event.relatedTarget;
while (relatedTarget) {
// 到父链上并检查 —— 我们是否还在 currentElem 内
// 然后发现,这只是一个内部移动 —— 忽略它
if (relatedTarget == currentElem) return;
relatedTarget = relatedTarget.parentNode;
}
// 我们离开了 <td>。真的。
onLeave(currentElem);
currentElem = null;
};
// 任何处理进入/离开一个元素的函数
function onEnter(elem) {
elem.style.background = 'pink';
// 在文本区域显示它
text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
text.scrollTop = 1e6;
}
function onLeave(elem) {
elem.style.background = '';
// 在文本区域显示它
text.value += `out <- ${elem.tagName}.${elem.className}\n`;
text.scrollTop = 1e6;
}
4、上述代码实现的功能: (1)使用事件委托来处理表格中任何 | 的进入/ 离开。因此,它依赖于mouseover / mouseout 而不是mouseenter / leave,mouseenter /leave不会冒泡,因此也不允许事件委托。 (2)额外的事件,如在 | 的后代之间移动都会被过滤掉,因此onEnter / Leave仅在鼠标进入 / 离开 | 整体时才会运行。
| |