移动:mouseover/out,mouseenter/leave

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

Mouseover/mouseout,relatedTarget

当鼠标指针出现在一个元素上时,mouseover 事件就会发生,而 mouseout 事件则发生在鼠标指针离开时。

这些事件很特别,因为它们有 relatedTarget

对于 mouseover

  • event.target —— 是鼠标经过的那个元素。
  • event.relatedTarget —— 是鼠标上一次经过的元素。

mouseout 则与之相反:

  • event.target —— 是鼠标离开的元素。
  • event.relatedTarget —— 是当前指针位置下的(鼠标进入的)元素。

在下面示例中,每个特性都是一个元素。当你移动鼠标时你可以看到文本区域的鼠标事件。

每个事件都有关于元素的去向以及来源的信息。

结果
script.js
style.css
index.html
container.onmouseover = container.onmouseout = handler;

function handler(event) {

  function str(el) {
    if (!el) return "null"
    return el.className || el.tagName;
  }

  log.value += event.type + ': ' +
    'target=' + str(event.target) +
    ', relatedTarget=' + str(event.relatedTarget) + "\n";
  log.scrollTop = log.scrollHeight;

  if (event.type == 'mouseover') {
    event.target.style.background = 'pink'
  }
  if (event.type == 'mouseout') {
    event.target.style.background = ''
  }
}
body,
html {
  margin: 0;
  padding: 0;
}

#container {
  border: 1px solid brown;
  padding: 10px;
  width: 330px;
  margin-bottom: 5px;
  box-sizing: border-box;
}

#log {
  height: 120px;
  width: 350px;
  display: block;
  box-sizing: border-box;
}

[class^="smiley-"] {
  display: inline-block;
  width: 70px;
  height: 70px;
  border-radius: 50%;
  margin-right: 20px;
}

.smiley-green {
  background: #a9db7a;
  border: 5px solid #92c563;
  position: relative;
}

.smiley-green .left-eye {
  width: 18%;
  height: 18%;
  background: #84b458;
  position: relative;
  top: 29%;
  left: 22%;
  border-radius: 50%;
  float: left;
}

.smiley-green .right-eye {
  width: 18%;
  height: 18%;
  border-radius: 50%;
  position: relative;
  background: #84b458;
  top: 29%;
  right: 22%;
  float: right;
}

.smiley-green .smile {
  position: absolute;
  top: 67%;
  left: 16.5%;
  width: 70%;
  height: 20%;
  overflow: hidden;
}

.smiley-green .smile:after,
.smiley-green .smile:before {
  content: "";
  position: absolute;
  top: -50%;
  left: 0%;
  border-radius: 50%;
  background: #84b458;
  height: 100%;
  width: 97%;
}

.smiley-green .smile:after {
  background: #84b458;
  height: 80%;
  top: -40%;
  left: 0%;
}

.smiley-yellow {
  background: #eed16a;
  border: 5px solid #dbae51;
  position: relative;
}

.smiley-yellow .left-eye {
  width: 18%;
  height: 18%;
  background: #dba652;
  position: relative;
  top: 29%;
  left: 22%;
  border-radius: 50%;
  float: left;
}

.smiley-yellow .right-eye {
  width: 18%;
  height: 18%;
  border-radius: 50%;
  position: relative;
  background: #dba652;
  top: 29%;
  right: 22%;
  float: right;
}

.smiley-yellow .smile {
  position: absolute;
  top: 67%;
  left: 19%;
  width: 65%;
  height: 14%;
  background: #dba652;
  overflow: hidden;
  border-radius: 8px;
}

.smiley-red {
  background: #ee9295;
  border: 5px solid #e27378;
  position: relative;
}

.smiley-red .left-eye {
  width: 18%;
  height: 18%;
  background: #d96065;
  position: relative;
  top: 29%;
  left: 22%;
  border-radius: 50%;
  float: left;
}

.smiley-red .right-eye {
  width: 18%;
  height: 18%;
  border-radius: 50%;
  position: relative;
  background: #d96065;
  top: 29%;
  right: 22%;
  float: right;
}

.smiley-red .smile {
  position: absolute;
  top: 57%;
  left: 16.5%;
  width: 70%;
  height: 20%;
  overflow: hidden;
}

.smiley-red .smile:after,
.smiley-red .smile:before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0%;
  border-radius: 50%;
  background: #d96065;
  height: 100%;
  width: 97%;
}

.smiley-red .smile:after {
  background: #d96065;
  height: 80%;
  top: 60%;
  left: 0%;
}
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>

  <div id="container">
    <div class="smiley-green">
      <div class="left-eye"></div>
      <div class="right-eye"></div>
      <div class="smile"></div>
    </div>

    <div class="smiley-yellow">
      <div class="left-eye"></div>
      <div class="right-eye"></div>
      <div class="smile"></div>
    </div>

    <div class="smiley-red">
      <div class="left-eye"></div>
      <div class="right-eye"></div>
      <div class="smile"></div>
    </div>
  </div>

  <textarea id="log">Events will show up here!
</textarea>

  <script src="script.js"></script>

</body>
</html>
relatedTarget 可以为 null

relatedTarget 属性可以为 null

这很正常,而且意味着鼠标不是来源于另一个元素,而是窗口以外。或者是离开了窗口。

当我们在代码中使用 event.relatedTarget 时,我们应该记住这种可能性。如果我们访问 event.relatedTarget.tagName,那么就会出现错误。

事件频率

当有鼠标移动时,mousemove 事件就会被触发。但是这不意味着每个像素都会产生一个事件。

浏览器会一直检查鼠标的位置。如果它注意到鼠标变化了,那么就会触发相应的事件。

这意味着如果访问者非常快地移动鼠标,那么 DOM 元素就会被跳过:

如果鼠标从上面的 #FROM#TO 元素移动地非常快,那么中间的 <div>(或其中的一些)可能会被跳过。mouseout 事件可能会在 #FROM 上被触发,然后立即在 #TO 上触发 mouseover

这在实践中是有用的,因为可能会有许多中间元素。我们并不是真的想要处理每一个进入离开的过程。

另一方面,我们应该记住,我们不能假设鼠标会缓慢地从一个事件移动到另一个事件。是的,它可以“跳”。

特别是,光标可能从窗口外跳进页面的中间。此时 relatedTarget=null,这是因为鼠标来自“窗口外(nowhere)”:

在快速移动的情况下,中间元素可能不会触发事件。但如果鼠标进入元素(`mouseover`),当它离开时,就一定会触发`mouseout`。

在下面的测试区域进行“实时”查看。

这段 HTML 包含两个嵌套的 <div> 元素。如果鼠标快速通过它们,那么不会有事件发生,或者只有红色 div 的事件被触发,或者是绿色 div 的事件被触发。

也可以尝试将指针移动到红色的 div 上,然后指针快速地向下并通过绿色的 div。如果动作足够快,那么父元素将会被忽略。

结果
script.js
style.css
index.html
green.onmouseover = green.onmouseout = green.onmousemove = handler;

function handler(event) {
  let type = event.type;
  while (type < 11) type += ' ';

  log(type + " target=" + event.target.id)
  return false;
}


function clearText() {
  text.value = "";
  lastMessage = "";
}

let lastMessageTime = 0;
let lastMessage = "";
let repeatCounter = 1;

function log(message) {
  if (lastMessageTime == 0) lastMessageTime = new Date();

  let time = new Date();

  if (time - lastMessageTime > 500) {
    message = '------------------------------\n' + message;
  }

  if (message === lastMessage) {
    repeatCounter++;
    if (repeatCounter == 2) {
      text.value = text.value.trim() + ' x 2\n';
    } else {
      text.value = text.value.slice(0, text.value.lastIndexOf('x') + 1) + repeatCounter + "\n";
    }

  } else {
    repeatCounter = 1;
    text.value += message + "\n";
  }

  text.scrollTop = text.scrollHeight;

  lastMessageTime = time;
  lastMessage = message;
}
#green {
  height: 50px;
  width: 160px;
  background: green;
}

#red {
  height: 20px;
  width: 110px;
  background: red;
  color: white;
  font-weight: bold;
  padding: 5px;
  text-align: center;
  margin: 20px;
}

#text {
  font-size: 12px;
  height: 200px;
  width: 360px;
  display: block;
}
<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>

  <div id="green">
    <div id="red">Test</div>
  </div>

  <input onclick="clearText()" value="Clear" type="button">

  <textarea id="text"></textarea>

  <script src="script.js"></script>

</body>

</html>

进入子元素时的“额外” mouseout

想象一下 —— 鼠标指针进入一个元素。mouseover 被触发。然后光标进入一个子元素。有趣的是,在这种情况下 mouseout 会被触发。光标仍然在元素中,但我们从它那儿接收到了 mouseout 事件!

这听起来很奇怪,但很容易解释。

根据浏览器逻辑,鼠标光标在任意时间只会位于单个元素上 —— 嵌套最多的那个(而且是 z-index 最大的那个)。

因此如果它转到另一个元素(甚至是一个子代),那么它将离开先前的那个。就这么简单。

我们可以从以下示例中看到一个有趣的结果。

红色的 <div> 嵌套在蓝色的 <div> 中。蓝色的 <div>mouseover/out 处理器可以记录在文本区发生的所有事件。

尝试进入蓝色元素,然后鼠标移动到红色的上面 —— 然后观察事件:

结果
script.js
style.css
index.html
function mouselog(event) {
  text.value += event.type + ' [target: ' + event.target.className + ']\n'
  text.scrollTop = text.scrollHeight
}
.blue {
  background: blue;
  width: 160px;
  height: 160px;
  position: relative;
}

.red {
  background: red;
  width: 100px;
  height: 100px;
  position: absolute;
  left: 30px;
  top: 30px;
}

textarea {
  height: 100px;
  width: 400px;
  display: block;
}
<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>

  <div class="blue" onmouseover="mouselog(event)" onmouseout="mouselog(event)">
    <div class="red"></div>
  </div>

  <textarea id="text"></textarea>
  <input type="button" onclick="text.value=''" value="Clear">

  <script src="script.js"></script>

</body>

</html>
  1. 在进入蓝色层时 —— 我们获取到了 mouseover [target: blue]
  2. 之后从蓝色移动红色时 —— 我们获取到了 mouseout [target: blue](离开父元素)。
  3. …然后立即获取到的是 mouseover [target: red]

因此,对于不考虑 target 的处理器,这看起来就像是在 mouseout 事件中,鼠标离开了父元素(第 (2) 步),然后在第 (3) 步的 mouseover 事件中鼠标又回到了父元素上。

如果我们在进入/离开元素时执行一些动作,就会多执行很多“错误”操作。对于简单的事情可能不引人注目。但对于复杂的事情来说,会带来不必要的副作用。

我们可以通过使用 mouseenter/mouseleave 事件来解决这个问题。

Mouseenter 和 mouseleave 事件

mouseenter/mouseleave 事件类似于 mouseover/mouseout。当鼠标指针移入/移出元素时,它们也会被触发。

但有两个不同之处:

  1. 元素内部的转换不会有影响。
  2. mouseenter/mouseleave 事件不会冒泡。

这些事件在直觉上非常清晰。

当指针进入一个元素时 —— mouseenter 被触发,而它在元素内部的去向并不重要。只有当鼠标光标离开时,mouseleave 事件才会被触发。

如果我们做个相同的例子,但将 mouseenter/mouseleave 放在蓝色 <div> 中,再做相同的操作 —— 我们就会看到只有移入或移出蓝色 <div> 时,事件才会被触发。当鼠标进入红色元素,再回到蓝色元素时,不会有任何反应。子代被全部忽略。

结果
script.js
style.css
index.html
function log(event) {
  text.value += event.type + ' [target: ' + event.target.id + ']\n';
  text.scrollTop = text.scrollHeight;
}
#blue {
  background: blue;
  width: 160px;
  height: 160px;
  position: relative;
}

#red {
  background: red;
  width: 70px;
  height: 70px;
  position: absolute;
  left: 45px;
  top: 45px;
}

#text {
  display: block;
  height: 100px;
  width: 400px;
}
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>

  <div id="blue" onmouseenter="log(event)" onmouseleave="log(event)">
    <div id="red"></div>
  </div>

  <textarea id="text"></textarea>
  <input type="button" onclick="text.value=''" value="Clear">

  <script src="script.js"></script>

</body>

</html>

事件委托

mouseenter/leave 事件非常简单,也非常容易使用。但它们不会冒泡。因此我们不能用它们来进行事件委托。

想象我们想要为表单元来处理鼠标的移入/移除。有几百个表单元。

通常的解决方案是 —— 在 <table> 中设置处理器,并在那里处理事件。但 mouseenter/leave 不会冒泡。因此如果类似事件发生在 <td> 上,那么只有 <td> 上的处理器才能捕获到它。

只有在移入/移出整个表单时才会触发处理器处理 <table> 上的 mouseenter/leave。在它内部转换的任何信息都无法获取。

没问题 —— 我们使用 mouseover/mouseout

一个简单的处理器可能像这样:

// 高亮鼠标下的单元
table.onmouseover = function(event) {
  let target = event.target;
  target.style.background = 'pink';
};

table.onmouseout = function(event) {
  let target = event.target;
  target.style.background = '';
};
结果
script.js
style.css
index.html
table.onmouseover = function(event) {
  let target = event.target;
  target.style.background = 'pink';
  text.value += "mouseover " + target.tagName + "\n";
  text.scrollTop = text.scrollHeight;
};

table.onmouseout = function(event) {
  let target = event.target;
  target.style.background = '';
  text.value += "mouseout " + target.tagName + "\n";
  text.scrollTop = text.scrollHeight;
};
#text {
  display: block;
  height: 100px;
  width: 456px;
}

#table th {
  text-align: center;
  font-weight: bold;
}

#table td {
  width: 150px;
  white-space: nowrap;
  text-align: center;
  vertical-align: bottom;
  padding-top: 5px;
  padding-bottom: 12px;
}

#table .nw {
  background: #999;
}

#table .n {
  background: #03f;
  color: #fff;
}

#table .ne {
  background: #ff6;
}

#table .w {
  background: #ff0;
}

#table .c {
  background: #60c;
  color: #fff;
}

#table .e {
  background: #09f;
  color: #fff;
}

#table .sw {
  background: #963;
  color: #fff;
}

#table .s {
  background: #f60;
  color: #fff;
}

#table .se {
  background: #0c3;
  color: #fff;
}

#table .highlight {
  background: red;
}
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>


  <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>

  <textarea id="text"></textarea>

  <input type="button" onclick="text.value=''" value="Clear">

  <script src="script.js"></script>

</body>
</html>

进入到任何元素或者表格内的元素时,这些处理器都可以运行。

但我们只想处理整个 <td> 元素中的进出转换。高亮显示整个单元。我们不想处理 <td> 子级之间发生的转换。

其中一个解决方案:

  • 记住在变量中高亮显示的 <td>
  • mouseover —— 如果我们仍然在当前 <td> 内,则忽略该事件。
  • mouseout —— 如果没有离开 <td>,则忽略。

当我们在 <td> 的子代间移动时,会过滤掉“额外”事件。

以下是包含所有细节的完整示例:

结果
script.js
style.css
index.html
// <td> under the mouse right now (if any)
let currentElem = null;

table.onmouseover = function(event) {
  if (currentElem) {
    // before entering a new element, the mouse always leaves the previous one
    // if we didn't leave <td> yet, then we're still inside it, so can ignore the event
    return;
  }

  let target = event.target.closest('td');
  if (!target || !table.contains(target)) return;

  // yeah we're inside <td> now
  currentElem = target;
  target.style.background = 'pink';
};


table.onmouseout = function(event) {
  // if we're outside of any <td> now, then ignore the event
  if (!currentElem) return;

  // we're leaving the element -- where to? Maybe to a child element?
  let relatedTarget = event.relatedTarget;
  if (relatedTarget) { // possible: relatedTarget = null
    while (relatedTarget) {
      // go up the parent chain and check -- if we're still inside currentElem
      // then that's an internal transition -- ignore it
      if (relatedTarget == currentElem) return;
      relatedTarget = relatedTarget.parentNode;
    }
  }

  // we left the element. really.
  currentElem.style.background = '';
  currentElem = null;
};
#text {
  display: block;
  height: 100px;
  width: 456px;
}

#table th {
  text-align: center;
  font-weight: bold;
}

#table td {
  width: 150px;
  white-space: nowrap;
  text-align: center;
  vertical-align: bottom;
  padding-top: 5px;
  padding-bottom: 12px;
}

#table .nw {
  background: #999;
}

#table .n {
  background: #03f;
  color: #fff;
}

#table .ne {
  background: #ff6;
}

#table .w {
  background: #ff0;
}

#table .c {
  background: #60c;
  color: #fff;
}

#table .e {
  background: #09f;
  color: #fff;
}

#table .sw {
  background: #963;
  color: #fff;
}

#table .s {
  background: #f60;
  color: #fff;
}

#table .se {
  background: #0c3;
  color: #fff;
}

#table .highlight {
  background: red;
}
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>


  <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>

  <script src="script.js"></script>

</body>
</html>

尝试在表格单元之间或内部移动光标,太快或太慢都有问题。与之前不同的是只有 <td> 作为一个整体被高亮显示。

总结

我们讲述了 mouseovermouseoutmousemovemouseentermouseleave 事件。

值得注意的事情:

  • 鼠标的快速移动可以使 mouseover, mousemove, mouseout 跳过一些中间元素。
  • mouseover/out 事件和 mouseenter/leave 事件有一个额外的目标:relatedTarget。这是作为起点/终点的元素,是对 target 的补充。
  • 即使从父元素转到子元素时,mouseover/out 也会被触发。它们假设鼠标一次只会移入一个元素 —— 最深的那个。
  • mouseenter/leave 事件不会冒泡,而且当鼠标进入子元素时也不会被触发。它们只关注鼠标在整个元素的内部还是外部。

任务

重要程度: 5

编写一个带有 data-tooltip 属性元素提示工具的 JavaScript 函数。

就像任务 提示工具行为,但这里可以嵌套带有注释的元素。下面显示了嵌套最深的工具提示。

比如:

<div data-tooltip="Here – is the house interior" id="house">
  <div data-tooltip="Here – is the roof" id="roof"></div>
  ...
  <a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a>
</div>

在 iframe 中的结果:

P.S. 提示:同一时间只显示一个工具提示。

打开一个任务沙箱。

重要程度: 5

编写一个函数,仅在访问者将鼠标移动到它上面而非通过它时,才会显示元素上的工具提示。

换句话说,如果访问者把鼠标移动到元素上,然后停下 —— 显示工具提示。如果访问者将鼠标快速移过元素,那就不需要显示,谁想要多余的内容呢?

从技术上说,我们可以测量鼠标在元素上的经过速度,如果速度很慢,那么我们认为它在元素上并显示工具提示,如果速度太快 —— 那么我们就忽略它。

为它创建一个通用对象 new HoverIntent(options),加上 options

  • elem —— 要跟踪的元素。
  • over —— 如果鼠标缓慢地移动元素,调用该函数。
  • out —— 当鼠标离开元素时,调用函数(如果 over 被调用过了)。

在工具提示中使用此类对象的示例:

// 工具提示样本
let tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Tooltip";

// 对象将跟踪鼠标,并调用 over/out
new HoverIntent({
  elem,
  over() {
    tooltip.style.left = elem.getBoundingClientRect().left + 'px';
    tooltip.style.top = elem.getBoundingClientRect().bottom + 5 + 'px';
    document.body.append(tooltip);
  },
  out() {
    tooltip.remove();
  }
});

示例:

如果鼠标移动速度超过 “clock”,那么不会发生任何事件,如果速度很慢或者在它们上面停下来,那么就会有一个工具提示。

请注意:当光标在 clock 子元素之间移动时,工具提示不会“一闪而过(blink)”。

打开带有测试的沙箱。

算法看起来很简单:

  1. onmouseover/out 处理器放在元素上。也可以在这里使用 onmouseenter/leave,但它们并不常用,而且如果我们使用委托,它们就会无效。
  2. 当鼠标光标进入元素时,开始测量 mousemove 上的速度。
  3. 如果速度慢,则运行 over
  4. 之后如果离开了元素,而且 over 也被执行了,则运行 out

问题是:“如何测量速度?”

第一个想法是:每 100ms 运行一次我们的函数,并测量前一个坐标和新坐标之间的距离。如果它很小,那么速度就很小。

不幸的是,在 JavaScript 中无法获取“当前鼠标坐标”。没有像 getCurrentMouseCoordinates() 这样的函数。

获取坐标的唯一方法是监听鼠标事件,就像 mousemove

因此我们可以在 mousemove 上设置一个处理器来跟踪坐标并记住它们。然后我们可以比较它们,每 100ms 比较一次。

P.S. 请注意:解决方案测试使用 dispatchEvent 来查看工具提示是否正确。

使用沙箱的测试功能打开解决方案。

教程路线图

评论

在评论之前先阅读本内容…
  • 如果你发现教程有错误,或者有其他需要修改和提升的地方 — 请 提交一个 GitHub issue 或 pull request,而不是在这评论。
  • 如果你对教程的内容有不理解的地方 — 请详细说明。
  • 使用 <code> 标签插入只有几个词的代码,插入多行代码可以使用 <pre> 标签,对于超过 10 行的代码,建议你使用沙箱(plnkrJSBincodepen…)