2021年12月15日

移動: mouseover/out, mouseenter/leave

マウスが要素間を移動するときに起こるイベントについての詳細を見ていきましょう。

Mouseover/mouseout, relatedTarget

mouseoever イベントはマウスポインタが要素の上に来るときに発生し、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>
relatedTargetnull の可能性があります

relatedTarget プロパティは null の場合があります。

それは正常なことで、単にマウスが別の要素から来たのではなく、ウィンドウの外から来たことを意味します。もしくはウィンドウから出たことを意味します。

我々のコードで event.relatedTarget を使うときは,その可能性を心に留めておく必要があります。もし event.relatedTarget.tagName へアクセスすると、エラーになるでしょう。

イベントの頻度

mousemove イベントはマウスの移動時にトリガされます。しかし、すべてのピクセル単位の移動でイベントが発生する訳ではありません。

ブラウザは時々マウスの位置をチェックします。そして、もし変更に気づいた場合、イベントをトリガします。

つまり、訪問者がマウスをとても速く動かしている場合、DOM 要素はスキップされる可能性があることを意味します。:

もしもマウスが上に書いているように、 #FROM から #TO 要素へ非常に速く移動する場合、間にある <div> (やそれら) はスキップされる可能性があります。mouseout イベントは #FROM でトリガし、その後 #TO ですぐに mouseover をトリガするかもしれません。

実際には、これは間に多くの要素がある場合に役立ちます。 私たちは本当にそれぞれのIn/Outを処理したくはありません。

その反面、マウスがあるイベントから別のイベントへゆっくり移動することは想定できないことに留意する必要があります。そうではなく、それは “ジャンプ” できます。

特に、ウィンドウの外からページ中央にカーソルが移動することもあり得ます。そして、それは “どこからも” 来ていないので、relatedTarget=null です。:

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.

下のテストスタンドで、実際に確認してみてください。

HTMLは2つのネストされた <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 を考慮しないハンドラでは、(2)mouseout で親を離れ、(3)mouseover でそこへ戻ってきたように見えます。

要素の出入りの際にいくつかのアクションを実行する場合、多くの余分な “偽の” 実行が発生します。シンプルな物事に対して気づかない可能性があります。複雑な物事に対しては、望ましくない副作用を引き起こす可能性があります。

私たちは、代わりに mouseenter/mouseleave イベントを使用して修正できます。

イベント mouseenter と mouseleave

イベント mouseenter/mouseleavemouseover/mouseout のようなものです。それらもマウスポインタが要素を出入りするときにトリガされます。

違いが2つあります。:

  1. 要素内の遷移はカウントされません。
  2. イベント mouseenter/mouseleave はバブルしません。

これらのイベントは直感的に非常に明確です。

ポインタが要素に入るとき – mouseenter をトリガし、次に要素内でどこに行こうと関係はありません。mouseleave イベントはカーソルがそこを離れるときにだけトリガします。

同じ例を作りますが、青の <div>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>

イベント移譲 [$Event delegation]

イベント 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> の子の間で起こる遷移を処理したくありません。

解決策の1つは次のようになります:

  • 変数で、現在強調されている <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> だけが強調表示されています。

サマリ

私たちはイベント mouseover, mouseout, mousemove, mouseentermouseleave を説明しました。

注目すべきことは:

  • 速いマウス移動は mouseover, mousemove, mouseout に対し、中間要素をスキップすることができます。
  • イベント mouseover/outmouserenter/leaverelatedTarget という追加のターゲットを持っています。それは私たちが 来た/行く 要素であり、target と相補的な要素です。
  • イベント mouseover/out は親要素から子要素に移動してもトリガされます。 マウスは、一度に1つの要素、つまり最も深い要素を想定します。
  • イベント mouserenter/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. ヒント: 同時に1つのツールチップだけ表示します。

タスクのためのサンドボックスを開く

重要性: 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();
  }
});

デモ:

マウスをすばやく移動し、“時計” を横切った場合は何も起こりません。ゆっくり、もしくはその上で停止した場合、ツールチップになります。

注意: ツールチップはカーソルが時計のサブ要素の間を移動するときに “点滅” しません、

テストと一緒にサンドボックスを開く

アルゴリズムはシンプルです:

  1. 要素上に onmouseover/out ハンドラを置きます。また、ここでは onmouserenter/leave を使うこともできますが、汎用性が下がり、移譲を導入すると上手く動作しません。
  2. マウスカーソルが要素に入ったとき、mousemove で速度の計算を開始します。
  3. もし速度が遅い場合、over を実行します。
  4. その後、要素から出て、over が実行された場合には out を実行します。

質問: “どうやって速度を測る?”

最初のアイデア: 100ms 毎に関数を実行し、前後の座標間の距離を計算する方法です。もしそれが小さい場合、スピードは小さいです。

残念ながら、JavaScript で “現在のマウス座標” を取得する方法はありません。getCurrentMouseCoordinates() のような関数はありません。

座標を取得する唯一の方法は、mousemove のようにマウスイベントをリッスンすることです。

したがって、座標を追跡しそれを覚えるために mousermove のハンドラを設定できます。

P.S. 注意: 解決策のテストでは、dispatchEvent を使用して、ツールチップが正しく動作するかを確認します。

サンドボックスでテストと一緒に解答を開く

チュートリアルマップ