我们将深入研究鼠标在元素之间移动时发生的事件。

事件 mouseover/mouseout,relatedTarget

当鼠标指针移到某个元素上时,mouseover 事件就会发生,而当鼠标离开该元素时,mouseout 事件就会发生。
image.png
这些事件很特别,因为它们具有 relatedTarget 属性。此属性是对 target 的补充。当鼠标从一个元素离开并去往另一个元素时,其中一个元素就变成了 target,另一个就变成了 relatedTarget。
对于 mouseover:

  • event.target —— 是鼠标移过的那个元素。
  • event.relatedTarget —— 是鼠标来自的那个元素(relatedTarget → target)。

mouseout 则与之相反:

  • event.target —— 是鼠标离开的元素。
  • event.relatedTarget —— 是鼠标移动到的,当前指针位置下的元素(target → relatedTarget)。

在下面这个示例中,每张脸及其功能都是单独的元素。当你移动鼠标时,你可以在文本区域中看到鼠标事件。
每个事件都具有关于 target 和 relatedTarget 的信息:

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <div id="container">
  9. <div class="smiley-green">
  10. <div class="left-eye"></div>
  11. <div class="right-eye"></div>
  12. <div class="smile"></div>
  13. </div>
  14. <div class="smiley-yellow">
  15. <div class="left-eye"></div>
  16. <div class="right-eye"></div>
  17. <div class="smile"></div>
  18. </div>
  19. <div class="smiley-red">
  20. <div class="left-eye"></div>
  21. <div class="right-eye"></div>
  22. <div class="smile"></div>
  23. </div>
  24. </div>
  25. <textarea id="log">Events will show up here!
  26. </textarea>
  27. <script src="script.js"></script>
  28. </body>
  29. </html>
  1. body,
  2. html {
  3. margin: 0;
  4. padding: 0;
  5. }
  6. #container {
  7. border: 1px solid brown;
  8. padding: 10px;
  9. width: 330px;
  10. margin-bottom: 5px;
  11. box-sizing: border-box;
  12. }
  13. #log {
  14. height: 120px;
  15. width: 350px;
  16. display: block;
  17. box-sizing: border-box;
  18. }
  19. [class^="smiley-"] {
  20. display: inline-block;
  21. width: 70px;
  22. height: 70px;
  23. border-radius: 50%;
  24. margin-right: 20px;
  25. }
  26. .smiley-green {
  27. background: #a9db7a;
  28. border: 5px solid #92c563;
  29. position: relative;
  30. }
  31. .smiley-green .left-eye {
  32. width: 18%;
  33. height: 18%;
  34. background: #84b458;
  35. position: relative;
  36. top: 29%;
  37. left: 22%;
  38. border-radius: 50%;
  39. float: left;
  40. }
  41. .smiley-green .right-eye {
  42. width: 18%;
  43. height: 18%;
  44. border-radius: 50%;
  45. position: relative;
  46. background: #84b458;
  47. top: 29%;
  48. right: 22%;
  49. float: right;
  50. }
  51. .smiley-green .smile {
  52. position: absolute;
  53. top: 67%;
  54. left: 16.5%;
  55. width: 70%;
  56. height: 20%;
  57. overflow: hidden;
  58. }
  59. .smiley-green .smile:after,
  60. .smiley-green .smile:before {
  61. content: "";
  62. position: absolute;
  63. top: -50%;
  64. left: 0%;
  65. border-radius: 50%;
  66. background: #84b458;
  67. height: 100%;
  68. width: 97%;
  69. }
  70. .smiley-green .smile:after {
  71. background: #84b458;
  72. height: 80%;
  73. top: -40%;
  74. left: 0%;
  75. }
  76. .smiley-yellow {
  77. background: #eed16a;
  78. border: 5px solid #dbae51;
  79. position: relative;
  80. }
  81. .smiley-yellow .left-eye {
  82. width: 18%;
  83. height: 18%;
  84. background: #dba652;
  85. position: relative;
  86. top: 29%;
  87. left: 22%;
  88. border-radius: 50%;
  89. float: left;
  90. }
  91. .smiley-yellow .right-eye {
  92. width: 18%;
  93. height: 18%;
  94. border-radius: 50%;
  95. position: relative;
  96. background: #dba652;
  97. top: 29%;
  98. right: 22%;
  99. float: right;
  100. }
  101. .smiley-yellow .smile {
  102. position: absolute;
  103. top: 67%;
  104. left: 19%;
  105. width: 65%;
  106. height: 14%;
  107. background: #dba652;
  108. overflow: hidden;
  109. border-radius: 8px;
  110. }
  111. .smiley-red {
  112. background: #ee9295;
  113. border: 5px solid #e27378;
  114. position: relative;
  115. }
  116. .smiley-red .left-eye {
  117. width: 18%;
  118. height: 18%;
  119. background: #d96065;
  120. position: relative;
  121. top: 29%;
  122. left: 22%;
  123. border-radius: 50%;
  124. float: left;
  125. }
  126. .smiley-red .right-eye {
  127. width: 18%;
  128. height: 18%;
  129. border-radius: 50%;
  130. position: relative;
  131. background: #d96065;
  132. top: 29%;
  133. right: 22%;
  134. float: right;
  135. }
  136. .smiley-red .smile {
  137. position: absolute;
  138. top: 57%;
  139. left: 16.5%;
  140. width: 70%;
  141. height: 20%;
  142. overflow: hidden;
  143. }
  144. .smiley-red .smile:after,
  145. .smiley-red .smile:before {
  146. content: "";
  147. position: absolute;
  148. top: 50%;
  149. left: 0%;
  150. border-radius: 50%;
  151. background: #d96065;
  152. height: 100%;
  153. width: 97%;
  154. }
  155. .smiley-red .smile:after {
  156. background: #d96065;
  157. height: 80%;
  158. top: 60%;
  159. left: 0%;
  160. }
  1. container.onmouseover = container.onmouseout = handler;
  2. function handler(event) {
  3. function str(el) {
  4. if (!el) return "null"
  5. return el.className || el.tagName;
  6. }
  7. log.value += event.type + ': ' +
  8. 'target=' + str(event.target) +
  9. ', relatedTarget=' + str(event.relatedTarget) + "\n";
  10. log.scrollTop = log.scrollHeight;
  11. if (event.type == 'mouseover') {
  12. event.target.style.background = 'pink'
  13. }
  14. if (event.type == 'mouseout') {
  15. event.target.style.background = ''
  16. }
  17. }

image.png
relatedTarget 可以为 null
relatedTarget 属性可以为 null。
这是正常现象,仅仅是意味着鼠标不是来自另一个元素,而是来自窗口之外。或者它离开了窗口。
当我们在代码中使用 event.relatedTarget 时,我们应该牢记这种可能性。如果我们访问 event.relatedTarget.tagName,那么就会出现错误。

跳过元素

当鼠标移动时,就会触发 mousemove 事件。但这并不意味着每个像素都会导致一个事件。
浏览器会一直检查鼠标的位置。如果发现了变化,就会触发事件。
这意味着,如果访问者非常快地移动鼠标,那么某些 DOM 元素就可能被跳过:
image.png
如果鼠标从上图所示的 #FROM 快速移动到 #TO 元素,则中间的

(或其中的一些)元素可能会被跳过。mouseout 事件可能会在 #FROM 上被触发,然后立即在 #TO 上触发 mouseover。
这对性能很有好处,因为可能有很多中间元素。我们并不真的想要处理每一个移入和离开的过程。
另一方面,我们应该记住,鼠标指针并不会“访问”所有元素。它可以“跳过”一些元素。
特别是,鼠标指针可能会从窗口外跳到页面的中间。在这种情况下,relatedTarget 为 null,因为它是从石头缝里蹦出来的(nowhere):
image.png
你可以在下面的测试台中“实时”查看。
它的 HTML 有两个嵌套的元素:
内部。如果将鼠标快速移动到它们上,则可能只有
或者只有
触发事件,或者根本没有事件触发。
还可以将鼠标指针移动到
中,然后将其快速向下移动过其父级元素。如果移动速度足够快,则父元素就会被忽略。鼠标会越过父元素而不会引起其注意。

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <div id="parent">parent
  9. <div id="child">child</div>
  10. </div>
  11. <textarea id="text"></textarea>
  12. <input onclick="clearText()" value="Clear" type="button">
  13. <script src="script.js"></script>
  14. </body>
  15. </html>
  1. #parent {
  2. background: #99C0C3;
  3. width: 160px;
  4. height: 120px;
  5. position: relative;
  6. }
  7. #child {
  8. background: #FFDE99;
  9. width: 50%;
  10. height: 50%;
  11. position: absolute;
  12. left: 50%;
  13. top: 50%;
  14. transform: translate(-50%, -50%);
  15. }
  16. textarea {
  17. height: 140px;
  18. width: 300px;
  19. display: block;
  20. }
  1. let parent = document.getElementById('parent');
  2. parent.onmouseover = parent.onmouseout = parent.onmousemove = handler;
  3. function handler(event) {
  4. let type = event.type;
  5. while (type.length < 11) type += ' ';
  6. log(type + " target=" + event.target.id)
  7. return false;
  8. }
  9. function clearText() {
  10. text.value = "";
  11. lastMessage = "";
  12. }
  13. let lastMessageTime = 0;
  14. let lastMessage = "";
  15. let repeatCounter = 1;
  16. function log(message) {
  17. if (lastMessageTime == 0) lastMessageTime = new Date();
  18. let time = new Date();
  19. if (time - lastMessageTime > 500) {
  20. message = '------------------------------\n' + message;
  21. }
  22. if (message === lastMessage) {
  23. repeatCounter++;
  24. if (repeatCounter == 2) {
  25. text.value = text.value.trim() + ' x 2\n';
  26. } else {
  27. text.value = text.value.slice(0, text.value.lastIndexOf('x') + 1) + repeatCounter + "\n";
  28. }
  29. } else {
  30. repeatCounter = 1;
  31. text.value += message + "\n";
  32. }
  33. text.scrollTop = text.scrollHeight;
  34. lastMessageTime = time;
  35. lastMessage = message;
  36. }

image.png
如果 mouseover 被触发了,则必须有 mouseout
在鼠标快速移动的情况下,中间元素可能会被忽略,但是我们可以肯定一件事:如果鼠标指针“正式地”进入了一个元素(生成了 mouseover 事件),那么一旦它离开,我们就会得到 mouseout。

当移动到一个子元素时 mouseout

mouseout 的一个重要功能 —— 当鼠标指针从元素移动到其后代时触发,例如在下面的这个 HTML 中,从 #parent 到 #child:

  1. <div id="parent">
  2. <div id="child">...</div>
  3. </div>

如果我们在 #parent 上,然后将鼠标指针更深入地移入 #child,在 #parent 上我们会得到 mouseout!
image.png
这听起来很奇怪,但很容易解释。
根据浏览器的逻辑,鼠标指针随时可能位于单个元素上 —— 嵌套最多的那个元素(z-index 最大的那个)。
因此,如果它转到另一个元素(甚至是一个后代),那么它将离开前一个元素。
请注意事件处理的另一个重要的细节。
后代的 mouseover 事件会冒泡。因此,如果 #parent 具有 mouseover 处理程序,它将被触发
image.png

你可以在下面这个示例中很清晰地看到这一点:

位于
内部。#parent 元素上有 mouseover/out 的处理程序,这些处理程序用于输出事件详细信息。
如果你将鼠标从 #parent 移动到 #child,那么你会看到在 #parent 上有两个事件:

  1. mouseout [target: parent](离开 parent),然后
  2. mouseover [target: child](来到 child,冒泡)。 ```html <!doctype html>

    parent
    child

  1. ```css
  2. #parent {
  3. background: #99C0C3;
  4. width: 160px;
  5. height: 120px;
  6. position: relative;
  7. }
  8. #child {
  9. background: #FFDE99;
  10. width: 50%;
  11. height: 50%;
  12. position: absolute;
  13. left: 50%;
  14. top: 50%;
  15. transform: translate(-50%, -50%);
  16. }
  17. textarea {
  18. height: 140px;
  19. width: 300px;
  20. display: block;
  21. }
  1. function mouselog(event) {
  2. let d = new Date();
  3. text.value += `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()} | ${event.type} [target: ${event.target.id}]\n`.replace(/(:|^)(\d\D)/, '$10$2');
  4. text.scrollTop = text.scrollHeight;
  5. }

image.png
如上例所示,当鼠标指针从 #parent 元素移动到 #child 时,会在父元素上触发两个处理程序:mouseout 和 mouseover:

  1. parent.onmouseout = function(event) {
  2. /* event.target: parent element */
  3. };
  4. parent.onmouseover = function(event) {
  5. /* event.target: child element (bubbled) */
  6. };

如果我们不检查处理程序中的 event.target,那么似乎鼠标指针离开了 #parent 元素,然后立即回到了它上面。
但是事实并非如此!鼠标指针仍然位于父元素上,它只是更深入地移入了子元素。
如果离开父元素时有一些行为(action),例如一个动画在 parent.onmouseout 中运行,当鼠标指针深入 #parent 时,我们并不希望发生这种行为。
为了避免它,我们可以在处理程序中检查 relatedTarget,如果鼠标指针仍在元素内,则忽略此类事件。
另外,我们可以使用其他事件:mouseenter 和 mouseleave,它们没有此类问题,接下来我们就对其进行详细介绍。

事件 mouseenter 和 mouseleave

事件 mouseenter/mouseleave 类似于 mouseover/mouseout。它们在鼠标指针进入/离开元素时触发。
但是有两个重要的区别:

  1. 元素内部与后代之间的转换不会产生影响。
  2. 事件 mouseenter/mouseleave 不会冒泡。

这些事件非常简单。
当鼠标指针进入一个元素时 —— 会触发 mouseenter。而鼠标指针在元素或其后代中的确切位置无关紧要。
当鼠标指针离开该元素时,事件 mouseleave 才会触发。
这个例子和上面的例子相似,但是现在最顶部的元素有 mouseenter/mouseleave 而不是 mouseover/mouseout。
正如你所看到的,唯一生成的事件是与将鼠标指针移入或移出顶部元素有关的事件。当鼠标指针进入 child 并返回时,什么也没发生。在后代之间的移动会被忽略。

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <div id="parent" onmouseenter="mouselog(event)" onmouseleave="mouselog(event)">parent
  9. <div id="child">child</div>
  10. </div>
  11. <textarea id="text"></textarea>
  12. <input type="button" onclick="text.value=''" value="Clear">
  13. <script src="script.js"></script>
  14. </body>
  15. </html>
  1. #parent {
  2. background: #99C0C3;
  3. width: 160px;
  4. height: 120px;
  5. position: relative;
  6. }
  7. #child {
  8. background: #FFDE99;
  9. width: 50%;
  10. height: 50%;
  11. position: absolute;
  12. left: 50%;
  13. top: 50%;
  14. transform: translate(-50%, -50%);
  15. }
  16. textarea {
  17. height: 140px;
  18. width: 300px;
  19. display: block;
  20. }
  1. function mouselog(event) {
  2. let d = new Date();
  3. text.value += `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()} | ${event.type} [target: ${event.target.id}]\n`.replace(/(:|^)(\d\D)/, '$10$2');
  4. text.scrollTop = text.scrollHeight;
  5. }

image.png

事件委托

事件 mouseenter/leave 非常简单且易用。但它们不会冒泡。因此,我们不能使用它们来进行事件委托。
假设我们要处理表格的单元格的鼠标进入/离开。并且这里有数百个单元格。
通常的解决方案是 —— 在

中设置处理程序,并在那里处理事件。但 mouseenter/leave 不会冒泡。因此,如果类似的事件发生在
上,那么只有 上的处理程序才能捕获到它。
上的 mouseenter/leave 的处理程序仅在鼠标指针进入/离开整个表格时才会触发。无法获取有关其内部移动的任何信息。
因此,让我们使用 mouseover/mouseout。
让我们从高亮显示鼠标指针下的元素的简单处理程序开始:

  1. // 高亮显示鼠标指针下的元素
  2. table.onmouseover = function(event) {
  3. let target = event.target;
  4. target.style.background = 'pink';
  5. };
  6. table.onmouseout = function(event) {
  7. let target = event.target;
  8. target.style.background = '';
  9. };

现在它们已经激活了。当鼠标在下面这个表格的各个元素上移动时,当前位于鼠标指针下的元素会被高亮显示:

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="stylesheet" href="style.css">
  6. </head>
  7. <body>
  8. <table id="table">
  9. <tr>
  10. <th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
  11. </tr>
  12. <tr>
  13. <td class="nw"><strong>Northwest</strong>
  14. <br>Metal
  15. <br>Silver
  16. <br>Elders
  17. </td>
  18. <td class="n"><strong>North</strong>
  19. <br>Water
  20. <br>Blue
  21. <br>Change
  22. </td>
  23. <td class="ne"><strong>Northeast</strong>
  24. <br>Earth
  25. <br>Yellow
  26. <br>Direction
  27. </td>
  28. </tr>
  29. <tr>
  30. <td class="w"><strong>West</strong>
  31. <br>Metal
  32. <br>Gold
  33. <br>Youth
  34. </td>
  35. <td class="c"><strong>Center</strong>
  36. <br>All
  37. <br>Purple
  38. <br>Harmony
  39. </td>
  40. <td class="e"><strong>East</strong>
  41. <br>Wood
  42. <br>Blue
  43. <br>Future
  44. </td>
  45. </tr>
  46. <tr>
  47. <td class="sw"><strong>Southwest</strong>
  48. <br>Earth
  49. <br>Brown
  50. <br>Tranquility
  51. </td>
  52. <td class="s"><strong>South</strong>
  53. <br>Fire
  54. <br>Orange
  55. <br>Fame
  56. </td>
  57. <td class="se"><strong>Southeast</strong>
  58. <br>Wood
  59. <br>Green
  60. <br>Romance
  61. </td>
  62. </tr>
  63. </table>
  64. <textarea id="text"></textarea>
  65. <input type="button" onclick="text.value=''" value="Clear">
  66. <script src="script.js"></script>
  67. </body>
  68. </html>
  1. #text {
  2. display: block;
  3. height: 100px;
  4. width: 456px;
  5. }
  6. #table th {
  7. text-align: center;
  8. font-weight: bold;
  9. }
  10. #table td {
  11. width: 150px;
  12. white-space: nowrap;
  13. text-align: center;
  14. vertical-align: bottom;
  15. padding-top: 5px;
  16. padding-bottom: 12px;
  17. cursor: pointer;
  18. }
  19. #table .nw {
  20. background: #999;
  21. }
  22. #table .n {
  23. background: #03f;
  24. color: #fff;
  25. }
  26. #table .ne {
  27. background: #ff6;
  28. }
  29. #table .w {
  30. background: #ff0;
  31. }
  32. #table .c {
  33. background: #60c;
  34. color: #fff;
  35. }
  36. #table .e {
  37. background: #09f;
  38. color: #fff;
  39. }
  40. #table .sw {
  41. background: #963;
  42. color: #fff;
  43. }
  44. #table .s {
  45. background: #f60;
  46. color: #fff;
  47. }
  48. #table .se {
  49. background: #0c3;
  50. color: #fff;
  51. }
  52. #table .highlight {
  53. background: red;
  54. }
  1. table.onmouseover = function(event) {
  2. let target = event.target;
  3. target.style.background = 'pink';
  4. text.value += `over -> ${target.tagName}\n`;
  5. text.scrollTop = text.scrollHeight;
  6. };
  7. table.onmouseout = function(event) {
  8. let target = event.target;
  9. target.style.background = '';
  10. text.value += `out <- ${target.tagName}\n`;
  11. text.scrollTop = text.scrollHeight;
  12. };

image.png
在我们的例子中,我们想要处理表格的单元格

之间的移动:进入一个单元格并离开它。我们对其他移动并不感兴趣,例如在单元格内部或在所有单元格的外部。让我们把这些过滤掉。
我们可以这样做:

  • 在变量中记住当前被高亮显示的
,让我们称它为 currentElem。
  • mouseover —— 如果我们仍然在当前的
  • 中,则忽略该事件。
  • mouseout —— 如果没有离开当前的
  • ,则忽略。

    这是说明所有可能情况的代码示例:

    1. // 现在位于鼠标下方的 <td>(如果有)
    2. let currentElem = null;
    3. table.onmouseover = function(event) {
    4. // 在进入一个新的元素前,鼠标总是会先离开前一个元素
    5. // 如果设置了 currentElem,那么我们就没有鼠标所悬停在的前一个 <td>,
    6. // 忽略此事件
    7. if (currentElem) return;
    8. let target = event.target.closest('td');
    9. // 我们移动到的不是一个 <td> —— 忽略
    10. if (!target) return;
    11. // 现在移动到了 <td> 上,但在处于了我们表格的外部(可能因为是嵌套的表格)
    12. // 忽略
    13. if (!table.contains(target)) return;
    14. // 给力!我们进入了一个新的 <td>
    15. currentElem = target;
    16. onEnter(currentElem);
    17. };
    18. table.onmouseout = function(event) {
    19. // 如果我们现在处于所有 <td> 的外部,则忽略此事件
    20. // 这可能是一个表格内的移动,但是在 <td> 外,
    21. // 例如从一个 <tr> 到另一个 <tr>
    22. if (!currentElem) return;
    23. // 我们将要离开这个元素 —— 去哪儿?可能是去一个后代?
    24. let relatedTarget = event.relatedTarget;
    25. while (relatedTarget) {
    26. // 到父链上并检查 —— 我们是否还在 currentElem 内
    27. // 然后发现,这只是一个内部移动 —— 忽略它
    28. if (relatedTarget == currentElem) return;
    29. relatedTarget = relatedTarget.parentNode;
    30. }
    31. // 我们离开了 <td>。真的。
    32. onLeave(currentElem);
    33. currentElem = null;
    34. };
    35. // 任何处理进入/离开一个元素的函数
    36. function onEnter(elem) {
    37. elem.style.background = 'pink';
    38. // 在文本区域显示它
    39. text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
    40. text.scrollTop = 1e6;
    41. }
    42. function onLeave(elem) {
    43. elem.style.background = '';
    44. // 在文本区域显示它
    45. text.value += `out <- ${elem.tagName}.${elem.className}\n`;
    46. text.scrollTop = 1e6;
    47. }

    再次,重要的功能是:

    1. 它使用事件委托来处理表格中任何
    的进入/离开。因此,它依赖于 mouseover/out 而不是 mouseenter/leave,mouseenter/leave 不会冒泡,因此也不允许事件委托。
  • 额外的事件,例如在
  • 的后代之间移动都会被过滤掉,因此 onEnter/Leave 仅在鼠标指针进入/离开 整体时才会运行。

    这是带有所有详细信息的完整示例:

    1. <!DOCTYPE HTML>
    2. <html>
    3. <head>
    4. <meta charset="utf-8">
    5. <link rel="stylesheet" href="style.css">
    6. </head>
    7. <body>
    8. <table id="table">
    9. <tr>
    10. <th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
    11. </tr>
    12. <tr>
    13. <td class="nw"><strong>Northwest</strong>
    14. <br>Metal
    15. <br>Silver
    16. <br>Elders
    17. </td>
    18. <td class="n"><strong>North</strong>
    19. <br>Water
    20. <br>Blue
    21. <br>Change
    22. </td>
    23. <td class="ne"><strong>Northeast</strong>
    24. <br>Earth
    25. <br>Yellow
    26. <br>Direction
    27. </td>
    28. </tr>
    29. <tr>
    30. <td class="w"><strong>West</strong>
    31. <br>Metal
    32. <br>Gold
    33. <br>Youth
    34. </td>
    35. <td class="c"><strong>Center</strong>
    36. <br>All
    37. <br>Purple
    38. <br>Harmony
    39. </td>
    40. <td class="e"><strong>East</strong>
    41. <br>Wood
    42. <br>Blue
    43. <br>Future
    44. </td>
    45. </tr>
    46. <tr>
    47. <td class="sw"><strong>Southwest</strong>
    48. <br>Earth
    49. <br>Brown
    50. <br>Tranquility
    51. </td>
    52. <td class="s"><strong>South</strong>
    53. <br>Fire
    54. <br>Orange
    55. <br>Fame
    56. </td>
    57. <td class="se"><strong>Southeast</strong>
    58. <br>Wood
    59. <br>Green
    60. <br>Romance
    61. </td>
    62. </tr>
    63. </table>
    64. <textarea id="text"></textarea>
    65. <input type="button" onclick="text.value=''" value="Clear">
    66. <script src="script.js"></script>
    67. </body>
    68. </html>
    1. #text {
    2. display: block;
    3. height: 100px;
    4. width: 456px;
    5. }
    6. #table th {
    7. text-align: center;
    8. font-weight: bold;
    9. }
    10. #table td {
    11. width: 150px;
    12. white-space: nowrap;
    13. text-align: center;
    14. vertical-align: bottom;
    15. padding-top: 5px;
    16. padding-bottom: 12px;
    17. cursor: pointer;
    18. }
    19. #table .nw {
    20. background: #999;
    21. }
    22. #table .n {
    23. background: #03f;
    24. color: #fff;
    25. }
    26. #table .ne {
    27. background: #ff6;
    28. }
    29. #table .w {
    30. background: #ff0;
    31. }
    32. #table .c {
    33. background: #60c;
    34. color: #fff;
    35. }
    36. #table .e {
    37. background: #09f;
    38. color: #fff;
    39. }
    40. #table .sw {
    41. background: #963;
    42. color: #fff;
    43. }
    44. #table .s {
    45. background: #f60;
    46. color: #fff;
    47. }
    48. #table .se {
    49. background: #0c3;
    50. color: #fff;
    51. }
    52. #table .highlight {
    53. background: red;
    54. }
    1. // 现在位于鼠标下方的 <td>(如果有)
    2. let currentElem = null;
    3. table.onmouseover = function(event) {
    4. // 在进入一个新的元素前,鼠标总是会先离开前一个元素
    5. // 如果设置了 currentElem,那么我们就没有鼠标所悬停在的前一个 <td>,
    6. // 忽略此事件
    7. if (currentElem) return;
    8. let target = event.target.closest('td');
    9. // 我们移动到的不是一个 <td> —— 忽略
    10. if (!target) return;
    11. // 现在移动到了 <td> 上,但在处于了我们表格的外部(可能因为是嵌套的表格)
    12. // 忽略
    13. if (!table.contains(target)) return;
    14. // 给力!我们进入了一个新的 <td>
    15. currentElem = target;
    16. onEnter(currentElem);
    17. };
    18. table.onmouseout = function(event) {
    19. // 如果我们现在处于所有 <td> 的外部,则忽略此事件
    20. // 这可能是一个表格内的移动,但是在 <td> 外,
    21. // 例如从一个 <tr> 到另一个 <tr>
    22. if (!currentElem) return;
    23. // 我们将要离开这个元素 —— 去哪儿?可能是去一个后代?
    24. let relatedTarget = event.relatedTarget;
    25. while (relatedTarget) {
    26. // 到父链上并检查 —— 我们是否还在 currentElem 内
    27. // 然后发现,这只是一个内部移动 —— 忽略它
    28. if (relatedTarget == currentElem) return;
    29. relatedTarget = relatedTarget.parentNode;
    30. }
    31. // 我们离开了 <td>。真的。
    32. onLeave(currentElem);
    33. currentElem = null;
    34. };
    35. // 任何处理进入/离开一个元素的函数
    36. function onEnter(elem) {
    37. elem.style.background = 'pink';
    38. // 在文本区域显示它
    39. text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
    40. text.scrollTop = 1e6;
    41. }
    42. function onLeave(elem) {
    43. elem.style.background = '';
    44. // 在文本区域显示它
    45. text.value += `out <- ${elem.tagName}.${elem.className}\n`;
    46. text.scrollTop = 1e6;
    47. }

    image.png
    尝试将鼠标指针移入和移出表格单元格及其内部。快还是慢都没关系。与前面的示例不同,只有

    被作为一个整体高亮显示。