Javascript 移動(dòng)鼠標(biāo):mouseover/out,mouseenter/leave

2023-02-17 10:54 更新

我們將深入研究鼠標(biāo)在元素之間移動(dòng)時(shí)發(fā)生的事件。

事件 mouseover/mouseout,relatedTarget

當(dāng)鼠標(biāo)指針移到某個(gè)元素上時(shí),mouseover 事件就會(huì)發(fā)生,而當(dāng)鼠標(biāo)離開(kāi)該元素時(shí),mouseout 事件就會(huì)發(fā)生。


這些事件很特別,因?yàn)樗鼈兙哂?nbsp;relatedTarget 屬性。此屬性是對(duì) target 的補(bǔ)充。當(dāng)鼠標(biāo)從一個(gè)元素離開(kāi)并去往另一個(gè)元素時(shí),其中一個(gè)元素就變成了 target,另一個(gè)就變成了 relatedTarget。

對(duì)于 mouseover

  • ?event.target? —— 是鼠標(biāo)移過(guò)的那個(gè)元素。
  • ?event.relatedTarget? —— 是鼠標(biāo)來(lái)自的那個(gè)元素(?relatedTarget? → ?target?)。

mouseout 則與之相反:

  • ?event.target? —— 是鼠標(biāo)離開(kāi)的元素。
  • ?event.relatedTarget? —— 是鼠標(biāo)移動(dòng)到的,當(dāng)前指針位置下的元素(?target? → ?relatedTarget?)。

在下面這個(gè)示例中,每張臉及其功能都是單獨(dú)的元素。當(dāng)你移動(dòng)鼠標(biāo)時(shí),你可以在文本區(qū)域中看到鼠標(biāo)事件。

每個(gè)事件都具有關(guān)于 target 和 relatedTarget 的信息:

  • index.html
  • <!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>
  • style.css
  • 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%;
    }
  • script.js
  • 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 = ''
      }
    }

?relatedTarget? 可以為 ?null?

relatedTarget 屬性可以為 null

這是正?,F(xiàn)象,僅僅是意味著鼠標(biāo)不是來(lái)自另一個(gè)元素,而是來(lái)自窗口之外?;蛘咚x開(kāi)了窗口。

當(dāng)我們?cè)诖a中使用 event.relatedTarget 時(shí),我們應(yīng)該牢記這種可能性。如果我們?cè)L問(wèn) event.relatedTarget.tagName,那么就會(huì)出現(xiàn)錯(cuò)誤。

跳過(guò)元素

當(dāng)鼠標(biāo)移動(dòng)時(shí),就會(huì)觸發(fā) mousemove 事件。但這并不意味著每個(gè)像素都會(huì)導(dǎo)致一個(gè)事件。

瀏覽器會(huì)一直檢查鼠標(biāo)的位置。如果發(fā)現(xiàn)了變化,就會(huì)觸發(fā)事件。

這意味著,如果訪問(wèn)者非??斓匾苿?dòng)鼠標(biāo),那么某些 DOM 元素就可能被跳過(guò):


如果鼠標(biāo)從上圖所示的 #FROM 快速移動(dòng)到 #TO 元素,則中間的 <div>(或其中的一些)元素可能會(huì)被跳過(guò)。mouseout 事件可能會(huì)在 #FROM 上被觸發(fā),然后立即在 #TO 上觸發(fā) mouseover。

這對(duì)性能很有好處,因?yàn)榭赡苡泻芏嘀虚g元素。我們并不真的想要處理每一個(gè)移入和離開(kāi)的過(guò)程。

另一方面,我們應(yīng)該記住,鼠標(biāo)指針并不會(huì)“訪問(wèn)”所有元素。它可以“跳過(guò)”一些元素。

特別是,鼠標(biāo)指針可能會(huì)從窗口外跳到頁(yè)面的中間。在這種情況下,relatedTarget 為 null,因?yàn)樗菑氖^縫里蹦出來(lái)的(nowhere):


它的 HTML 有兩個(gè)嵌套的元素:<div id="child"> 在 <div id="parent"> 內(nèi)部。如果將鼠標(biāo)快速移動(dòng)到它們上,則可能只有 <div id="child"> 或者只有 <div id="parent"> 觸發(fā)事件,或者根本沒(méi)有事件觸發(fā)。

還可以將鼠標(biāo)指針移動(dòng)到 <div id="child"> 中,然后將其快速向下移動(dòng)過(guò)其父級(jí)元素。如果移動(dòng)速度足夠快,則父元素就會(huì)被忽略。鼠標(biāo)會(huì)越過(guò)父元素而不會(huì)引起其注意。

  • index.html
  • <!doctype html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
    
      <div id="parent">parent
        <div id="child">child</div>
      </div>
      <textarea id="text"></textarea>
      <input onclick="clearText()" value="Clear" type="button">
    
      <script src="script.js"></script>
    
    </body>
    
    </html>
  • style.css
  • #parent {
      background: #99C0C3;
      width: 160px;
      height: 120px;
      position: relative;
    }
    
    #child {
      background: #FFDE99;
      width: 50%;
      height: 50%;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    textarea {
      height: 140px;
      width: 300px;
      display: block;
    }
  • script.js
  • let parent = document.getElementById('parent');
    parent.onmouseover = parent.onmouseout = parent.onmousemove = handler;
    
    function handler(event) {
      let type = event.type;
      while (type.length < 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;
    }

如果 ?mouseover? 被觸發(fā)了,則必須有 ?mouseout?

在鼠標(biāo)快速移動(dòng)的情況下,中間元素可能會(huì)被忽略,但是我們可以肯定一件事:如果鼠標(biāo)指針“正式地”進(jìn)入了一個(gè)元素(生成了 mouseover 事件),那么一旦它離開(kāi),我們就會(huì)得到 mouseout。

當(dāng)移動(dòng)到一個(gè)子元素時(shí) mouseout

mouseout 的一個(gè)重要功能 —— 當(dāng)鼠標(biāo)指針從元素移動(dòng)到其后代時(shí)觸發(fā),例如在下面的這個(gè) HTML 中,從 #parent 到 #child

<div id="parent">
  <div id="child">...</div>
</div>

如果我們?cè)?nbsp;#parent 上,然后將鼠標(biāo)指針更深入地移入 #child,在 #parent 上我們會(huì)得到 mouseout!


這聽(tīng)起來(lái)很奇怪,但很容易解釋。

根據(jù)瀏覽器的邏輯,鼠標(biāo)指針隨時(shí)可能位于單個(gè)元素上 —— 嵌套最多的那個(gè)元素(z-index 最大的那個(gè))。

因此,如果它轉(zhuǎn)到另一個(gè)元素(甚至是一個(gè)后代),那么它將離開(kāi)前一個(gè)元素。

請(qǐng)注意事件處理的另一個(gè)重要的細(xì)節(jié)。

后代的 mouseover 事件會(huì)冒泡。因此,如果 #parent 具有 mouseover 處理程序,它將被觸發(fā):


你可以在下面這個(gè)示例中很清晰地看到這一點(diǎn):<div id="child"> 位于 <div id="parent"> 內(nèi)部。#parent 元素上有 mouseover/out 的處理程序,這些處理程序用于輸出事件詳細(xì)信息。

如果你將鼠標(biāo)從 #parent 移動(dòng)到 #child,那么你會(huì)看到在 #parent 上有兩個(gè)事件:

  1. ?mouseout [target: parent]?(離開(kāi) parent),然后
  2. ?mouseover [target: child]?(來(lái)到 child,冒泡)。
  • index.html
  • <!doctype html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
    
      <div id="parent" onmouseover="mouselog(event)" onmouseout="mouselog(event)">parent
        <div id="child">child</div>
      </div>
    
      <textarea id="text"></textarea>
      <input type="button" onclick="text.value=''" value="Clear">
    
      <script src="script.js"></script>
    
    </body>
    
    </html>
  • style.css
  • #parent {
      background: #99C0C3;
      width: 160px;
      height: 120px;
      position: relative;
    }
    
    #child {
      background: #FFDE99;
      width: 50%;
      height: 50%;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    textarea {
      height: 140px;
      width: 300px;
      display: block;
    }
  • script.js
  • 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;
    }

如上例所示,當(dāng)鼠標(biāo)指針從 #parent 元素移動(dòng)到 #child 時(shí),會(huì)在父元素上觸發(fā)兩個(gè)處理程序:mouseout 和 mouseover

parent.onmouseout = function(event) {
  /* event.target: parent element */
};
parent.onmouseover = function(event) {
  /* event.target: child element (bubbled) */
};

如果我們不檢查處理程序中的 event.target,那么似乎鼠標(biāo)指針離開(kāi)了 #parent 元素,然后立即回到了它上面。

但是事實(shí)并非如此!鼠標(biāo)指針仍然位于父元素上,它只是更深入地移入了子元素。

如果離開(kāi)父元素時(shí)有一些行為(action),例如一個(gè)動(dòng)畫(huà)在 parent.onmouseout 中運(yùn)行,當(dāng)鼠標(biāo)指針深入 #parent 時(shí),我們并不希望發(fā)生這種行為。

為了避免它,我們可以在處理程序中檢查 relatedTarget,如果鼠標(biāo)指針仍在元素內(nèi),則忽略此類事件。

另外,我們可以使用其他事件:mouseenter 和 mouseleave,它們沒(méi)有此類問(wèn)題,接下來(lái)我們就對(duì)其進(jìn)行詳細(xì)介紹。

事件 mouseenter 和 mouseleave

事件 mouseenter/mouseleave 類似于 mouseover/mouseout。它們?cè)谑髽?biāo)指針進(jìn)入/離開(kāi)元素時(shí)觸發(fā)。

但是有兩個(gè)重要的區(qū)別:

  1. 元素內(nèi)部與后代之間的轉(zhuǎn)換不會(huì)產(chǎn)生影響。
  2. 事件 ?mouseenter/mouseleave? 不會(huì)冒泡。

這些事件非常簡(jiǎn)單。

當(dāng)鼠標(biāo)指針進(jìn)入一個(gè)元素時(shí) —— 會(huì)觸發(fā) mouseenter。而鼠標(biāo)指針在元素或其后代中的確切位置無(wú)關(guān)緊要。

當(dāng)鼠標(biāo)指針離開(kāi)該元素時(shí),事件 mouseleave 才會(huì)觸發(fā)。

這個(gè)例子和上面的例子相似,但是現(xiàn)在最頂部的元素有 mouseenter/mouseleave 而不是 mouseover/mouseout。

正如你所看到的,唯一生成的事件是與將鼠標(biāo)指針移入或移出頂部元素有關(guān)的事件。當(dāng)鼠標(biāo)指針進(jìn)入 child 并返回時(shí),什么也沒(méi)發(fā)生。在后代之間的移動(dòng)會(huì)被忽略。

  • index.html
  • <!doctype html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
    
      <div id="parent" onmouseenter="mouselog(event)" onmouseleave="mouselog(event)">parent
        <div id="child">child</div>
      </div>
    
      <textarea id="text"></textarea>
      <input type="button" onclick="text.value=''" value="Clear">
    
      <script src="script.js"></script>
    
    </body>
    
    </html>
  • style.css
  • #parent {
      background: #99C0C3;
      width: 160px;
      height: 120px;
      position: relative;
    }
    
    #child {
      background: #FFDE99;
      width: 50%;
      height: 50%;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
    
    textarea {
      height: 140px;
      width: 300px;
      display: block;
    }
  • script.js
  • 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;
    }

事件委托

事件 mouseenter/leave 非常簡(jiǎn)單且易用。但它們不會(huì)冒泡。因此,我們不能使用它們來(lái)進(jìn)行事件委托。

假設(shè)我們要處理表格的單元格的鼠標(biāo)進(jìn)入/離開(kāi)。并且這里有數(shù)百個(gè)單元格。

通常的解決方案是 —— 在 <table> 中設(shè)置處理程序,并在那里處理事件。但 mouseenter/leave 不會(huì)冒泡。因此,如果類似的事件發(fā)生在 <td> 上,那么只有 <td> 上的處理程序才能捕獲到它。

<table> 上的 mouseenter/leave 的處理程序僅在鼠標(biāo)指針進(jìn)入/離開(kāi)整個(gè)表格時(shí)才會(huì)觸發(fā)。無(wú)法獲取有關(guān)其內(nèi)部移動(dòng)的任何信息。

因此,讓我們使用 mouseover/mouseout

讓我們從高亮顯示鼠標(biāo)指針下的元素的簡(jiǎn)單處理程序開(kāi)始:

// 高亮顯示鼠標(biāo)指針下的元素
table.onmouseover = function(event) {
  let target = event.target;
  target.style.background = 'pink';
};

table.onmouseout = function(event) {
  let target = event.target;
  target.style.background = '';
};

現(xiàn)在它們已經(jīng)激活了。當(dāng)鼠標(biāo)在下面這個(gè)表格的各個(gè)元素上移動(dòng)時(shí),當(dāng)前位于鼠標(biāo)指針下的元素會(huì)被高亮顯示:

示例代碼

在我們的例子中,我們想要處理表格的單元格 <td> 之間的移動(dòng):進(jìn)入一個(gè)單元格并離開(kāi)它。我們對(duì)其他移動(dòng)并不感興趣,例如在單元格內(nèi)部或在所有單元格的外部。讓我們把這些過(guò)濾掉。

我們可以這樣做:

  • 在變量中記住當(dāng)前被高亮顯示的 ?<td>?,讓我們稱它為 ?currentElem?。
  • ?mouseover? —— 如果我們?nèi)匀辉诋?dāng)前的 ?<td>? 中,則忽略該事件。
  • ?mouseout? —— 如果沒(méi)有離開(kāi)當(dāng)前的 ?<td>?,則忽略。

這是說(shuō)明所有可能情況的代碼示例:

// 現(xiàn)在位于鼠標(biāo)下方的 <td>(如果有)
let currentElem = null;

table.onmouseover = function(event) {
  // 在進(jìn)入一個(gè)新的元素前,鼠標(biāo)總是會(huì)先離開(kāi)前一個(gè)元素
  // 如果設(shè)置了 currentElem,那么我們就沒(méi)有鼠標(biāo)所懸停在的前一個(gè) <td>,
  // 忽略此事件
  if (currentElem) return;

  let target = event.target.closest('td');

  // 我們移動(dòng)到的不是一個(gè) <td> —— 忽略
  if (!target) return;

  // 現(xiàn)在移動(dòng)到了 <td> 上,但在處于了我們表格的外部(可能因?yàn)槭乔短椎谋砀瘢?  // 忽略
  if (!table.contains(target)) return;

  // 給力!我們進(jìn)入了一個(gè)新的 <td>
  currentElem = target;
  onEnter(currentElem);
};


table.onmouseout = function(event) {
  // 如果我們現(xiàn)在處于所有 <td> 的外部,則忽略此事件
  // 這可能是一個(gè)表格內(nèi)的移動(dòng),但是在 <td> 外,
  // 例如從一個(gè) <tr> 到另一個(gè) <tr>
  if (!currentElem) return;

  // 我們將要離開(kāi)這個(gè)元素 —— 去哪兒?可能是去一個(gè)后代?
  let relatedTarget = event.relatedTarget;

  while (relatedTarget) {
    // 到父鏈上并檢查 —— 我們是否還在 currentElem 內(nèi)
    // 然后發(fā)現(xiàn),這只是一個(gè)內(nèi)部移動(dòng) —— 忽略它
    if (relatedTarget == currentElem) return;

    relatedTarget = relatedTarget.parentNode;
  }

  // 我們離開(kāi)了 <td>。真的。
  onLeave(currentElem);
  currentElem = null;
};

// 任何處理進(jìn)入/離開(kāi)一個(gè)元素的函數(shù)
function onEnter(elem) {
  elem.style.background = 'pink';

  // 在文本區(qū)域顯示它
  text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
  text.scrollTop = 1e6;
}

function onLeave(elem) {
  elem.style.background = '';

  // 在文本區(qū)域顯示它
  text.value += `out <- ${elem.tagName}.${elem.className}\n`;
  text.scrollTop = 1e6;
}

再次,重要的功能是:

  1. 它使用事件委托來(lái)處理表格中任何 ?<td>? 的進(jìn)入/離開(kāi)。因此,它依賴于 ?mouseover/out? 而不是 ?mouseenter/leave?,?mouseenter/leave? 不會(huì)冒泡,因此也不允許事件委托。
  2. 額外的事件,例如在 ?<td>? 的后代之間移動(dòng)都會(huì)被過(guò)濾掉,因此 ?onEnter/Leave? 僅在鼠標(biāo)指針進(jìn)入/離開(kāi) ?<td>? 整體時(shí)才會(huì)運(yùn)行。

這是帶有所有詳細(xì)信息的完整示例:

完整示例

嘗試將鼠標(biāo)指針移入和移出表格單元格及其內(nèi)部。快還是慢都沒(méi)關(guān)系。與前面的示例不同,只有 <td> 被作為一個(gè)整體高亮顯示。

總結(jié)

我們講了 mouseovermouseout,mousemove,mouseenter 和 mouseleave 事件。

以下這些內(nèi)容要注意:

  • 快速移動(dòng)鼠標(biāo)可能會(huì)跳過(guò)中間元素。
  • ?mouseover/out? 和 ?mouseenter/leave? 事件還有一個(gè)附加屬性:?relatedTarget?。這就是我們來(lái)自/到的元素,是對(duì) ?target? 的補(bǔ)充。

即使我們從父元素轉(zhuǎn)到子元素時(shí),也會(huì)觸發(fā) mouseover/out 事件。瀏覽器假定鼠標(biāo)一次只會(huì)位于一個(gè)元素上 —— 最深的那個(gè)。

mouseenter/leave 事件在這方面不同:它們僅在鼠標(biāo)進(jìn)入和離開(kāi)元素時(shí)才觸發(fā)。并且它們不會(huì)冒泡。

任務(wù)


改進(jìn)的工具提示行為

重要程度: 5

編寫(xiě) JavaScript,在帶有 data-tooltip 特性(attribute)的元素上顯示一個(gè)工具提示。該特性的值應(yīng)該成為工具提示的文本。

與任務(wù) 工具提示行為 類似,但這里可以嵌套帶有注解(annotated)的元素。并且顯示的是嵌套最深的工具提示。

同一時(shí)間只能顯示一個(gè)工具提示。

例如:

<div data-tooltip="這是房子的內(nèi)部" id="house">
  <div data-tooltip="這里是屋頂" id="roof"></div>
  ...
  <a  rel="external nofollow" target="_blank"  data-tooltip="Read on…">鼠標(biāo)懸浮在我上</a>
</div>

在 iframe 中的結(jié)果:


打開(kāi)一個(gè)任務(wù)沙箱。


解決方案

使用沙箱打開(kāi)解決方案。


“智能”工具提示

重要程度: 5

編寫(xiě)一個(gè)函數(shù),該函數(shù)僅在訪問(wèn)者將鼠標(biāo) 移至 元素而不是 移過(guò) 元素的情況下,在該元素上顯示工具提示。

換句話說(shuō),如果訪問(wèn)者將鼠標(biāo)移至元素上,并停下來(lái) —— 顯示工具提示。如果他們只是將鼠標(biāo)移過(guò)元素,那就沒(méi)必要顯示,誰(shuí)想要多余的閃爍呢?

從技術(shù)上說(shuō),我們可以測(cè)量元素上的鼠標(biāo)移動(dòng)速度,如果速度很慢,那么我們就假定它 在元素上,并顯示工具提示,如果速度很快 —— 那么我們就忽略它。

為此,我們創(chuàng)建一個(gè)通用對(duì)象 new HoverIntent(options)。

其 options

  • ?elem? —— 要跟蹤的元素。
  • ?over? —— 鼠標(biāo)移動(dòng)到元素上時(shí)要調(diào)用的函數(shù):即,鼠標(biāo)在元素上的移動(dòng)速度很慢,或者停在該元素上。
  • ?out? —— 當(dāng)鼠標(biāo)離開(kāi)元素時(shí)調(diào)用的函數(shù)(如果 ?over? 已經(jīng)被調(diào)用過(guò)了)。

在工具提示中使用此類對(duì)象的示例:

// 一個(gè)簡(jiǎn)單的工具提示
let tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Tooltip";

// 該對(duì)象將跟蹤鼠標(biāo),并調(diào)用 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();
  }
});

如果你將鼠標(biāo)快速地從“時(shí)鐘”上移動(dòng)過(guò)去,那么什么都不會(huì)發(fā)生,如果你使用鼠標(biāo)在“時(shí)鐘”上慢慢移動(dòng),或者停在“時(shí)鐘”上,則會(huì)出現(xiàn)一個(gè)工具提示。

請(qǐng)注意:當(dāng)鼠標(biāo)指針在“時(shí)鐘”的元素之間移動(dòng)時(shí),工具提示不會(huì)“閃爍”

打開(kāi)帶有測(cè)試的沙箱。


解決方案

算法看起來(lái)很簡(jiǎn)單:

  1. 將 ?onmouseover/out? 處理程序放在元素上。在這里也可以使用 ?onmouseenter/leave?,但是它們的通用性較差,如果我們想引入事件委托時(shí),它則無(wú)法使用。
  2. 當(dāng)鼠標(biāo)指針進(jìn)入元素時(shí),開(kāi)始測(cè)量 ?mousemove? 上的速度。
  3. 如果速度慢,則運(yùn)行 ?over?。
  4. 當(dāng)我們的鼠標(biāo)指針要移出元素,并且 ?over? 也執(zhí)行了,則會(huì)運(yùn)行 ?out?。

但是如何測(cè)量速度?

第一個(gè)想法是:每 100ms 運(yùn)行一次函數(shù),并測(cè)量前坐標(biāo)和新坐標(biāo)之間的距離。如果很小,那么速度就很小。

不幸的是,在 JavaScript 中無(wú)法獲取“鼠標(biāo)當(dāng)前坐標(biāo)”。沒(méi)有像 getCurrentMouseCoordinates() 這樣的函數(shù)。

獲取坐標(biāo)的唯一方法是監(jiān)聽(tīng)例如 mousemove 這樣的鼠標(biāo)事件。

因此,我們可以在 mousemove 上設(shè)置一個(gè)處理程序來(lái)跟蹤坐標(biāo)并記住它們。然后我每 100ms 比較一次。

P.S. 請(qǐng)注意:解決方案測(cè)試使用 dispatchEvent 來(lái)檢查工具提示是否正確。

使用沙箱的測(cè)試功能打開(kāi)解決方案。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)