6 сентябр 2025

Sichqoncha harakati: mouseover/out, mouseenter/leave

Sichqoncha elementlar orasida harakatlanayotganda sodir bo’ladigan hodisalar haqida batafsil to’xtalib o’taylik.

Hodisalar mouseover/mouseout, relatedTarget

mouseover hodisasi sichqoncha ko’rsatkichi element ustiga kelganda sodir bo’ladi, mouseout esa uni tark etganda.

Bu hodisalar maxsus, chunki ularda relatedTarget xossasi mavjud. Bu xossa target ni to’ldiradi. Sichqoncha bitta elementdan ikkinchisiga o’tganda, ulardan biri target bo’ladi, ikkinchisi esa – relatedTarget.

mouseover uchun:

  • event.target – sichqoncha ustiga kelgan element.
  • event.relatedTarget – sichqoncha kelgan element (relatedTargettarget).

mouseout uchun aksincha:

  • event.target – sichqoncha tark etgan element.
  • event.relatedTarget – ko’rsatkich ostidagi yangi element, sichqoncha o’tgan joy (targetrelatedTarget).

Quyidagi misolda har bir yuz va uning xususiyatlari alohida elementlardir. Sichqonchani harakatlantirsangiz, matn maydonida sichqoncha hodisalarini ko’rishingiz mumkin.

Har bir hodisa target va relatedTarget haqida ma’lumotga ega:

Natija
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">
      Voqealar shu yerda chiqadi!
    </textarea>

    <script src="script.js"></script>
  </body>
</html>
relatedTarget null bo’lishi mumkin

relatedTarget xossasi null bo’lishi mumkin.

Bu normal va shunchaki sichqoncha boshqa elementdan emas, balki oynadan tashqaridan kelganini anglatadi. Yoki u oynani tark etganini.

Kodimizda event.relatedTarget dan foydalanganda bu imkoniyatni yodda tutishimiz kerak. Agar biz event.relatedTarget.tagName ga murojaat qilsak, xato yuz beradi.

Elementlarni o’tkazib yuborish

mousemove hodisasi sichqoncha harakatlanayotganda ishga tushadi. Lekin bu har bir piksel hodisaga olib kelishini anglatmaydi.

Brauzer vaqti-vaqti bilan sichqoncha pozitsiyasini tekshiradi. Va agar o’zgarishlarni sezsa, hodisalarni ishga tushiradi.

Bu shuni anglatadiki, agar tashrif buyuruvchi sichqonchani juda tez harakatlantirsa, ba’zi DOM-elementlar o’tkazib yuborilishi mumkin:

Agar sichqoncha yuqorida chizilgandek #FROM dan #TO elementlariga juda tez harakat qilsa, oraliq <div> elementlari (yoki ularning ba’zilari) o’tkazib yuborilishi mumkin. mouseout hodisasi #FROM da ishga tushishi va keyin darhol #TO da mouseover bo’lishi mumkin.

Bu unumdorlik uchun yaxshi, chunki ko’plab oraliq elementlar bo’lishi mumkin. Biz har biriga kirish va chiqishni qayta ishlamoqchi emasmiz.

Boshqa tomondan, sichqoncha ko’rsatkichi yo’l bo’ylab barcha elementlarni “tashrif buyurmasligini” yodda tutishimiz kerak. U “sakrashi” mumkin.

Xususan, ko’rsatkich oynadan tashqaridan sahifaning o’rtasiga to’g’ri sakrashi mumkin. Bunday holda relatedTarget null bo’ladi, chunki u “hech qayerdan” kelgan:

Buni quyidagi test stendida “jonli” tekshirishingiz mumkin.

Uning HTML ikkita ichki elementga ega: <div id="child"> <div id="parent"> ichida. Agar sichqonchani ular ustida tez harakatlantisangiz, ehtimol faqat bola div hodisalarni ishga tushiradi, yoki ehtimol ota-onasi, yoki ehtimol umuman hodisalar bo’lmaydi.

Shuningdek, ko’rsatkichni bola div ga olib boring, so’ngra ota-onasi orqali tezda pastga chiqaring. Agar harakat etarlicha tez bo’lsa, ota-ona elementi e’tiborga olinmaydi. Sichqoncha ota-ona elementini sezmasdan kesib o’tadi.

Natija
script.js
style.css
index.html
let parent = document.getElementById("parent");
parent.onmouseover = parent.onmouseout = parent.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;
}
#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;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div id="parent">
      ota
      <div id="child">bola</div>
    </div>
    <textarea id="text"></textarea>
    <input onclick="clearText()" value="Clear" type="button" />

    <script src="script.js"></script>
  </body>
</html>
Agar mouseover ishga tushsa, mouseout bo’lishi kerak

Tez sichqoncha harakatlarida oraliq elementlar e’tiborga olinmasligi mumkin, lekin bir narsani aniq bilamiz: agar ko’rsatkich elementga “rasmiy” ravishda kirsa (mouseover hodisasi hosil bo’lsa), uni tark etganda har doim mouseout ni olamiz.

Bola uchun ketayotganda mouseout

mouseout ning muhim xususiyati – u ko’rsatkich elementdan uning avlodiga o’tganda, masalan bu HTML da #parent dan #child ga o’tganda ishga tushadi:

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

Agar biz #parent da bo’lsak va keyin ko’rsatkichni #child ga chuqurroq olib borsak, #parent da mouseout ni olamiz!

Bu g’alati tuyulishi mumkin, lekin osonlikcha tushuntiriladi.

Brauzer mantiqiga ko’ra, sichqoncha kursori istalgan vaqtda faqat bitta element ustida bo’lishi mumkin – eng ichki va z-index bo’yicha eng yuqori.

Shuning uchun agar u boshqa elementga (hatto avlodga) o’tsa, oldingi birini tark etadi.

Hodisalarni qayta ishlashning yana bir muhim tafsilotiga e’tibor bering.

Avloddagi mouseover hodisasi yuqoriga bubble qiladi. Shuning uchun agar #parent da mouseover ishlov beruvchi bo’lsa, u ishga tushadi:

Buni quyidagi misolda juda yaxshi ko’rishingiz mumkin: <div id="child"> <div id="parent"> ichida. #parent elementida hodisa tafsilotlarini chiqaruvchi mouseover/out ishlov beruvchilari bor.

Agar sichqonchani #parent dan #child ga olib borsangiz, #parent da ikkita hodisani ko’rasiz:

  1. mouseout [target: parent] (ota-onani tark etdi), keyin
  2. mouseover [target: child] (bolaga keldi, bubble qildi).
Natija
script.js
style.css
index.html
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;
}
#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;
}
<!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>

Ko’rsatilgandek, ko’rsatkich #parent elementidan #child ga o’tganda, ota-ona elementida ikkita ishlov beruvchi ishga tushadi: mouseout va mouseover:

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

Agar biz ishlov beruvchilar ichida event.target ni tekshirmasak, sichqoncha ko’rsatkichi #parent elementini tark etgandek va keyin darhol uning ustiga qaytgandek tuyulishi mumkin.

Lekin bunday emas! Ko’rsatkich hali ham ota-ona ustida, u shunchaki bola elementiga chuqurroq o’tgan.

Agar ota-ona elementni tark etganda ba’zi harakatlar bo’lsa, masalan parent.onmouseout da animatsiya ishlasa, biz odatda ko’rsatkich shunchaki #parent ga chuqurroq kirganda buni xohlamaymiz.

Buni oldini olish uchun ishlov beruvchida relatedTarget ni tekshirishimiz va agar sichqoncha hali ham element ichida bo’lsa, bunday hodisani e’tiborsiz qoldirishimiz mumkin.

Shu kabi muammolarga ega bo’lmagan boshqa hodisalardan foydalanishimiz mumkin: mouseenter va mouseleave, bularni endi ko’rib chiqamiz.

Hodisalar mouseenter va mouseleave

mouseenter/mouseleave hodisalari mouseover/mouseout ga o’xshash. Ular sichqoncha ko’rsatkichi elementga kirganda/chiqayotganda ishga tushadi.

Lekin ikkita muhim farq bor:

  1. Element ichidagi, avlodlarga/dan o’tishlar hisobga olinmaydi.
  2. mouseenter/mouseleave hodisalari bubble qilmaydi.

Bu hodisalar juda oddiy.

Ko’rsatkich elementga kirganda – mouseenter ishga tushadi. Ko’rsatkichning element yoki uning avlodlari ichidagi aniq joylashuvi muhim emas.

Ko’rsatkich elementni tark etganda – mouseleave ishga tushadi.

Bu misol yuqoridagiga o’xshash, lekin endi yuqori elementda mouseover/mouseout o’rniga mouseenter/mouseleave bor.

Ko’rib turganimizdek, hosil bo’lgan yagona hodisalar ko’rsatkichni yuqori elementga kirish va chiqish bilan bog’liq. Ko’rsatkich bolaga o’tganda va orqaga qaytganda hech narsa bo’lmaydi. Avlodlar orasidagi o’tishlar e’tiborga olinmaydi

Natija
script.js
style.css
index.html
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;
}
#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;
}
<!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)"
    >
      ota
      <div id="child">bola</div>
    </div>

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

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

Hodisa delegatsiyasi

mouseenter/leave hodisalari juda oddiy va ishlatish oson. Lekin ular bubble qilmaydi. Shuning uchun biz ular bilan hodisa delegatsiyasidan foydalana olmaymiz.

Tasavvur qiling, biz jadval katakchalarida sichqoncha kirish/chiqishini qayta ishlamoqchimiz. Va yuzlab katak bor.

Tabiiy yechim – <table> ga ishlov beruvchi o’rnatish va hodisalarni u yerda qayta ishlash bo’ladi. Lekin mouseenter/leave bubble qilmaydi. Shuning uchun agar bunday hodisa <td> da sodir bo’lsa, faqat o’sha <td> dagi ishlov beruvchi uni ushlashi mumkin.

<table> dagi mouseenter/leave uchun ishlov beruvchilar faqat ko’rsatkich butun jadvalga kirganda/chiqayotganda ishga tushadi. Uning ichidagi o’tishlar haqida hech qanday ma’lumot olish mumkin emas.

Shuning uchun mouseover/mouseout dan foydalanamiz.

Sichqoncha ostidagi elementni ajratib ko’rsatuvchi oddiy ishlov beruvchilar bilan boshlaylik:

// sichqoncha ostidagi elementni ajratib ko'rsataylik
table.onmouseover = function(event) {
  let target = event.target;
  target.style.background = 'pink';
};

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

Mana ular amalda. Sichqoncha ushbu jadvalning elementlari bo’ylab harakatlanayotganda, joriy biri ajratiladi:

Natija
script.js
style.css
index.html
table.onmouseover = function (event) {
  let target = event.target;
  target.style.background = "pink";

  text.value += `over -> ${target.tagName}\n`;
  text.scrollTop = text.scrollHeight;
};

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

  text.value += `out <- ${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;
  cursor: pointer;
}

#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> jadvali: Yo'nalish, Element, Rang, Ma'no
        </th>
      </tr>
      <tr>
        <td class="nw">
          <strong>Shimoli-g'arbiy</strong> <br />Metal <br />Kumush
          <br />Oqsoqollar
        </td>
        <td class="n">
          <strong>Shimoliy</strong> <br />Suv <br />Ko'k <br />O'zgarish
        </td>
        <td class="ne">
          <strong>Shimoli-sharqiy</strong> <br />Yer <br />Sariq <br />Yo'nalish
        </td>
      </tr>
      <tr>
        <td class="w">
          <strong>G'arbiy</strong> <br />Metal <br />Oltin <br />Yoshlik
        </td>
        <td class="c">
          <strong>Markaz</strong> <br />Hammasi <br />Binafsha <br />Uyg'unlik
        </td>
        <td class="e">
          <strong>Sharqiy</strong> <br />Yog'och <br />Ko'k <br />Kelajak
        </td>
      </tr>
      <tr>
        <td class="sw">
          <strong>Janubi-g'arbiy</strong> <br />Yer <br />Jigarrang
          <br />Xotirjamlik
        </td>
        <td class="s">
          <strong>Janubiy</strong> <br />Olov <br />To'q sariq
          <br />Shon-shuhrat
        </td>
        <td class="se">
          <strong>Janubi-sharqiy</strong> <br />Yog'och <br />Yashil <br />Sevgi
        </td>
      </tr>
    </table>

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

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

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

Bizning holatda biz jadval katakchalarida <td> o’tishlarni qayta ishlamoqchimiz: katakka kirish va uni tark etish. Boshqa o’tishlar, masalan katak ichida yoki hech qanday katakdan tashqarida, bizni qiziqtirmaydi. Keling, ularni filtrlaylik.

Mana nima qilishimiz mumkin:

  • Hozirgi ajratilgan <td> ni o’zgaruvchida eslab qolish, uni currentElem deb ataymiz.
  • mouseover da – agar biz hali ham joriy <td> ichida bo’lsak, hodisani e’tiborsiz qoldirish.
  • mouseout da – agar joriy <td> ni tark etmagan bo’lsak, e’tiborsiz qoldirish.

Mana barcha mumkin bo’lgan holatlarni hisobga olgan kod misoli:

// Hozir ajratilgan element
let currentElem = null;

table.onmouseover = function(event) {
  // mouseover ishga tushganda va biz allaqachon biror elementni ajratgan bo'lsak,
  // unda avvalgi ajratishni bekor qilishdan oldin yangi hodisani e'tiborsiz qoldiramiz
  if (currentElem) return;

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

  // Biz td ga o'tmadik - e'tiborsiz qoldirish
  if (!target) return;

  // Biz ichki jadvalga o'tdik (mumkin), uni e'tiborsiz qoldirish
  if (!table.contains(target)) return;

  // Hurray! Biz yangi td ga kirdik
  currentElem = target;
  onEnter(currentElem);
};


table.onmouseout = function(event) {
  // Agar biz hozirda hech qanday elementda bo'lmasak, e'tiborsiz qoldirish
  if (!currentElem) return;

  // Biz elementni tark etayotgiz - qayerga? Ehtimol avlodga?
  let relatedTarget = event.relatedTarget;

  while (relatedTarget) {
    // Yo'q! Biz currentElem ning ichiga o'tdik - bu ichki o'tish, e'tiborsiz qoldirish
    if (relatedTarget == currentElem) return;

    relatedTarget = relatedTarget.parentNode;
  }

  // Biz haqiqatdan ham elementni tark etdik
  onLeave(currentElem);
  currentElem = null;
};

// har qanday elementga kirish/chiqish funksiyalari
function onEnter(elem) {
  elem.style.background = 'pink';

  // textarea da buni ko'rsatish
  text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
  text.scrollTop = text.scrollHeight;
}

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

  // textarea da buni ko'rsatish
  text.value += `leave <- ${elem.tagName}.${elem.className}\n`;
  text.scrollTop = text.scrollHeight;
}

Yana bir bor, muhim xususiyatlar:

  1. U jadval ichidagi har qanday <td> ga kirish/chiqishni qayta ishlash uchun hodisa delegatsiyasidan foydalanadi. Shuning uchun u bubble qilmaydigan va shuning uchun delegatsiyaga ruxsat bermaydigan mouseenter/leave o’rniga mouseover/out ga tayangan.
  2. <td> avlodlari orasida harakatlanish kabi qo’shimcha hodisalar filtrlangan, shuning uchun onEnter/Leave faqat ko’rsatkich <td> ni butunlay tark etganda yoki kirganda ishlaydi.

Mana barcha tafsilotlar bilan to’liq misol:

Natija
script.js
style.css
index.html
// <td> hozir sichqoncha ostida (agar mavjud bo'lsa)
let currentElem = null;

table.onmouseover = function (event) {
  // yangi elementni kiritishdan oldin sichqoncha har doim oldingi elementni tark etadi
  // agar currentElem o'rnatilgan bo'lsa, biz oldingi <td> dan chiqmadik,
  // bu uning ichida sichqonchani bosish, hodisani e'tiborsiz qoldiring
  if (currentElem) return;

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

  // biz <td> ga ko'chdik - e'tibor bermang
  if (!target) return;

  // <td> ga ko'chirildi, lekin jadvalimizdan tashqarida (jadvallar o'rnatilgan bo'lsa mumkin)
  // e'tibor bermang
  if (!table.contains(target)) return;

  // urrraaa! biz yangi <td> ga kirdik
  currentElem = target;
  onEnter(currentElem);
};

table.onmouseout = function (event) {
  // agar biz hozir biron bir <td> dan tashqarida bo'lsak, hodisaga e'tibor bermang
  // bu jadval ichidagi harakat, lekin <td> dan tashqarida,
  // masalan. <tr> dan boshqa <tr> ga
  if (!currentElem) return;

  // biz elementni qoldiramiz – qayerga? Balki vorisigadir?
  let relatedTarget = event.relatedTarget;

  while (relatedTarget) {
    // ota-ona zanjiriga o'ting va biz hali ham currentElem ichida ekanligimizni tekshiring
    // keyin bu ichki o'tish - unga e'tibor bermang
    if (relatedTarget == currentElem) return;

    relatedTarget = relatedTarget.parentNode;
  }

  // biz <td> ni tark etdik.
  onLeave(currentElem);
  currentElem = null;
};

// elementni kiritish/chiqish bilan bog'liq har qanday funktsiyalar
function onEnter(elem) {
  elem.style.background = "pink";

  // buni matn maydonida ko'rsating
  text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
  text.scrollTop = 1e6;
}

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

  // buni matn maydonida ko'rsating
  text.value += `out <- ${elem.tagName}.${elem.className}\n`;
  text.scrollTop = 1e6;
}
#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;
  cursor: pointer;
}

#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> jadvali: Yo'nalish, Element, Rang, Ma'no
        </th>
      </tr>
      <tr>
        <td class="nw">
          <strong>Shimoli-g'arbiy</strong> <br />Metal <br />Kumush
          <br />Oqsoqollar
        </td>
        <td class="n">
          <strong>Shimoliy</strong> <br />Suv <br />Ko'k <br />O'zgarish
        </td>
        <td class="ne">
          <strong>Shimoli-sharqiy</strong> <br />Yer <br />Sariq <br />Yo'nalish
        </td>
      </tr>
      <tr>
        <td class="w">
          <strong>G'arbiy</strong> <br />Metal <br />Oltin <br />Yoshlik
        </td>
        <td class="c">
          <strong>Markaz</strong> <br />Hammasi <br />Binafsha <br />Uyg'unlik
        </td>
        <td class="e">
          <strong>Sharqiy</strong> <br />Yog'och <br />Ko'k <br />Kelajak
        </td>
      </tr>
      <tr>
        <td class="sw">
          <strong>Janubi-g'arbiy</strong> <br />Yer <br />Jigarrang
          <br />Xotirjamlik
        </td>
        <td class="s">
          <strong>Janubiy</strong> <br />Olov <br />To'q sariq
          <br />Shon-shuhrat
        </td>
        <td class="se">
          <strong>Janubi-sharqiy</strong> <br />Yog'och <br />Yashil <br />Sevgi
        </td>
      </tr>
    </table>

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

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

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

Kursorni jadval katakchalariga va ichiga kirish va chiqishda harakatlantiring. Tez yoki sekin – farqi yo’q. Oldingi misoldan farqli o’laroq, faqat <td> butunlay ajratiladi.

Xulosa

Biz mouseover, mouseout, mousemove, mouseenter va mouseleave hodisalarini ko’rib chiqdik.

Quyidagi narsalarni qayd etish yaxshi:

  • Tez sichqoncha harakati oraliq elementlarni o’tkazib yuborishi mumkin.
  • mouseover/out va mouseenter/leave hodisalari qo’shimcha xossaga ega: relatedTarget. Bu biz kelayotgan/borayotgan element, target ga qo’shimcha.

mouseover/out hodisalari ota-ona elementdan bola elementga o’tganimizda ham ishga tushadi. Brauzer sichqoncha bir vaqtda faqat bitta element ustida bo’lishi mumkin deb hisoblaydi – eng chuqur biri.

mouseenter/leave hodisalari bu jihatdan boshqacha: ular faqat sichqoncha elementga butunlay kirganda va chiqayotganda ishga tushadi. Shuningdek, ular bubble qilmaydi.

Vazifalar

muhimlik: 5

data-tooltip atributiga ega boʻlgan element ustidan maslahat koʻrsatuvchi JavaScript yozing. Ushbu atributning qiymati asboblar maslahati matniga aylanishi kerak.

Bu Tooltip xatti-harakati topshirig’iga o’xshaydi, lekin bu erda izohli elementlarni joylashtirish mumkin. Eng chuqur joylashtirilgan asboblar maslahati ko’rsatilgan.

Bir vaqtning o’zida faqat bitta maslahat ko’rsatilishi mumkin.

Masalan; misol uchun:

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

Ifreymdagi natija:

Vazifa uchun sandbox-ni oching.

Faqat tashrif buyuruvchi sichqonchani elementga olib kelganda ko’rsatiladigan, lekin orqali o’tkazganda ko’rsatilmaydigan tooltip funksiyasini yozing.

Boshqacha qilib aytganda, agar tashrif buyuruvchi sichqonchani elementga olib kelib, u yerda to’xtatsa – tooltipni ko’rsating. Va agar ular shunchaki sichqonchani orqali o’tkazgan bo’lsa, kerak emas, kim qo’shimcha miltillashni xohlaydi?

Texnik jihatdan, biz element ustida sichqoncha tezligini o’lchashimiz mumkin va agar u sekin bo’lsa, u “element ustiga kelgan” deb hisoblaymiz va tooltipni ko’rsatamiz, agar tez bo’lsa – uni e’tiborsiz qoldiramiz.

Buning uchun universal obyekt new HoverIntent(options) yarating.

Uning options lari:

  • elem – kuzatish uchun element.
  • over – agar sichqoncha elementga kelgan bo’lsa chaqiriladigan funksiya: ya’ni u sekin harakat qiladi yoki uning ustida to’xtaydi.
  • out – sichqoncha elementni tark etganda chaqiriladigan funksiya (over chaqirilgan bo’lsa).

Bunday obyektni tooltip uchun ishlatish misoli:

// tooltip namunasi
let tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Tooltip";

// obyekt sichqonchani kuzatadi va over/out ni chaqiradi
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();
  }
});

Demo:

Agar siz sichqonchani “soat” ustiga tez harakat qildirsangiz, hech narsa bo’lmaydi, va agar uni sekin qilsangiz yoki ularda to’xtatsangiz, tooltip paydo bo’ladi.

Diqqat qiling: kursor soat kichik elementlari orasida harakat qilganda tooltip “miltillamaydi”.

Sinovlar bilan sandbox-ni oching.

Algoritm oddiy ko’rinadi:

  1. Elementga onmouseover/out ishlov beruvchilarini o’rnating. Bu yerda onmouseenter/leave dan ham foydalanish mumkin, lekin ular kamroq universal, agar delegatsiyani joriy qilsak ishlamaydi.
  2. Sichqoncha kursori elementga kirganda, mousemove da tezlikni o’lchashni boshlang.
  3. Agar tezlik sekin bo’lsa, over ni ishga tushiring.
  4. Elementdan chiqayotganda va over bajarilgan bo’lsa, out ni ishga tushiring.

Lekin tezlikni qanday o’lchash kerak?

Birinchi g’oya quyidagicha bo’lishi mumkin: har 100ms da funksiyani ishga tushirish va oldingi va yangi koordinatalar orasidagi masofani o’lchash. Agar u kichik bo’lsa, tezlik kichik.

Afsuski, JavaScript da “joriy sichqoncha koordinatalarini” olishning imkoni yo’q. getCurrentMouseCoordinates() kabi funksiya yo’q.

Koordinatalarni olishning yagona yo’li – mousemove kabi sichqoncha hodisalarini tinglash va koordinatalarni hodisa obyektidan olish.

Shuning uchun koordinatalarni kuzatish va eslab qolish uchun mousemove ga ishlov beruvchi o’rnatamiz. Keyin ularni har 100ms da bir marta solishtiramiz.

P.S. Diqqat qiling: yechim testlari tooltip to’g’ri ishlayotganini ko’rish uchun dispatchEvent dan foydalanadi.

Yechimni sandbox-dagi sinovlar bilan oching.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <code> yorlig'ini ishlating, bir nechta satrlar uchun - ularni <pre> yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)