Skip to content

Moving the mouse: mouseover/out, mouseenter/leave #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Sep 13, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<style>
body {
height: 2000px;
/* the tooltip should work after page scroll too */
/* تولتیپ باید بعد از پیمایش صفحه هم کار کند */
}

.tooltip {
Expand Down Expand Up @@ -49,42 +49,42 @@
<body>


<div data-tooltip="Here is the house interior" id="house">
<div data-tooltip="Here is the roof" id="roof"></div>
<div data-tooltip="این نمای داخلی خانه است" id="house">
<div data-tooltip="این سقف است" id="roof"></div>

<p>روزی روزگاری مامان خوکه سه تا بچه کوچک داشت.</p>

<p>Once upon a time there was a mother pig who had three little pigs.</p>
<p>اون سه تا خوک کوچک انقدر بزرگ شدند که مادرشون به اونها گفت: "شما خیلی بزرگ شدید و دیگه نمی‌تونید اینجا زندگی کنید. باید برید و خونه‌های خودتون رو بسازید. اما مراقب باشید که گرگه شمارو شکار نکنه."</p>

<p>سه خوک کوچک راه افتادند و رفتند. اونها به مادرشون گفتند: "ما مراقب هستیم تا گرگه مارو شکار نکنه."</p>

<p>The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

<p>The three little pigs set off. "We will take care that the wolf does not catch us," they said.</p>

<p>Soon they met a man. <a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a></p>
<p>بعد از مدت کوتاهی مردی رو دیدند. <a href="https://fa.wikipedia.org/wiki/%D8%B3%D9%87_%D8%AE%D9%88%DA%A9_%D9%81%D8%B3%D9%82%D9%84%DB%8C" data-tooltip="ادامه را بخوانید">اشاره‌گر را روی من بیار</a></p>

</div>

<script>
let tooltip;

document.onmouseover = function(event) {
// important: a fast-moving mouse may "jump" right to a child on an annotated node, skipping the parent
// so mouseover may happen on a child.
// نکته مهم: یک حرکت سریع اشاره‌گر موس ممکن است با نادیده گرفتن پدر، مستقیما روی خود فرزند "پرش" کند
// پس رویداد mouseover ممکن است روی فرزند اتفاق بیفتد.

let anchorElem = event.target.closest('[data-tooltip]');

if (!anchorElem) return;

// show tooltip and remember it
// نمایش تولتیپ و ذخیره کردن آن در یک متغیر
tooltip = showTooltip(anchorElem, anchorElem.dataset.tooltip);
}

document.onmouseout = function() {
// it is possible that mouseout triggered, but we're still inside the element
// (its target was inside, and it bubbled)
// but in this case we'll have an immediate mouseover,
// so the tooltip will be destroyed and shown again
// ممکن است که mouseout اتفاق بیفتد ولی ما هنوز داخل عنصر باشیم.
// (یعنی که target داخل عنصر دیگری بوده، و رویداد بالا رفته است)
// اما در این صورت ما یک mouseover لحظه‌ای داریم،
// پس تولتیپ ازبین می‌رود و دوباره نمایش داده می‌شود
//
// luckily, the "blinking" won't be visible,
// as both events happen almost at the same time
// خوشبختانه، این "چشمک زدن" معلوم نیست.
// چون هر دو رویداد تقریبا در یک زمان واحد اتفاق می‌افتند
if (tooltip) {
tooltip.remove();
tooltip = false;
Expand All @@ -101,7 +101,7 @@

let coords = anchorElem.getBoundingClientRect();

// position the tooltip over the center of the element
// نمایش تولتیپ وسط عنصر
let left = coords.left + (anchorElem.offsetWidth - tooltipElem.offsetWidth) / 2;
if (left < 0) left = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,21 @@
<body>


<div data-tooltip="Here is the house interior" id="house">
<div data-tooltip="Here is the roof" id="roof"></div>
<div data-tooltip="این نمای داخلی خانه است" id="house">
<div data-tooltip="این سقف است" id="roof"></div>

<p>روزی روزگاری مامان خوکه سه تا بچه کوچک داشت.</p>

<p>Once upon a time there was a mother pig who had three little pigs.</p>
<p>اون سه تا خوک کوچک انقدر بزرگ شدند که مادرشون به اونها گفت: "شما خیلی بزرگ شدید و دیگه نمی‌تونید اینجا زندگی کنید. باید برید و خونه‌های خودتون رو بسازید. اما مراقب باشید که گرگه شمارو شکار نکنه."</p>

<p>سه خوک کوچک راه افتادند و رفتند. اونها به مادرشون گفتند: "ما مراقب هستیم تا گرگه مارو شکار نکنه."</p>

<p>The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

<p>The three little pigs set off. "We will take care that the wolf does not catch us," they said.</p>

<p>Soon they met a man. <a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a></p>
<p>بعد از مدت کوتاهی مردی رو دیدند. <a href="https://fa.wikipedia.org/wiki/%D8%B3%D9%87_%D8%AE%D9%88%DA%A9_%D9%81%D8%B3%D9%82%D9%84%DB%8C" data-tooltip="ادامه را بخوانید">اشاره‌گر را روی من بیار</a></p>

</div>

<script>
// ...your code...
// ...کد شما...
</script>

</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ importance: 5

---

# Improved tooltip behavior
# رفتار یک تولتیپ پیشرفته

Write JavaScript that shows a tooltip over an element with the attribute `data-tooltip`. The value of this attribute should become the tooltip text.
کد جاوا اسکریپیتی بنویسید که یک تولتیپ را با استفاده از صفت `data-tooltip` بالای عنصر نمایش دهد. مقدار این صفت باید متن تولتیپ باشد.

That's like the task <info:task/behavior-tooltip>, but here the annotated elements can be nested. The most deeply nested tooltip is shown.
این تکلیف شبیه تکلیف <info:task/behavior-tooltip> است، اما عناصری که توضیح اضافه دارند، می‌توانند تو در تو باشند. به این صورت که داخلی‌ترین تولتیپ باید نمایش داده شود.

Only one tooltip may show up at the same time.
فقط یک تولتیپ می‌تواند در لحظه باید نمایان باشد.

For instance:
برای مثال:

```html
<div data-tooltip="Hereis the house interior" id="house">
<div data-tooltip="Hereis the roof" id="roof"></div>
<div data-tooltip="اینجانمای داخلی خانه است" id="house">
<div data-tooltip="اینجاسقف است" id="roof"></div>
...
<a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a>
<a href="https://fa.wikipedia.org/wiki/%D8%B3%D9%87_%D8%AE%D9%88%DA%A9_%D9%81%D8%B3%D9%82%D9%84%DB%8C" data-tooltip="ادامه را بخوانید">اشاره‌گر را روی من بیار</a>
</div>
```

The result in iframe:
نتیجه داخل iframe:

[iframe src="solution" height=300 border=1]
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@

The algorithm looks simple:
1. Put `onmouseover/out` handlers on the element. Also can use `onmouseenter/leave` here, but they are less universal, won't work if we introduce delegation.
2. When a mouse cursor entered the element, start measuring the speed on `mousemove`.
3. If the speed is slow, then run `over`.
4. When we're going out of the element, and `over` was executed, run `out`.
الگوریتم ساده به نظر می‌رسد:
1. برای رویدادهای `onmouseover/out` کنترل‌کننده‌هایی روی عنصر تعریف می‌کنیم. همچنین می‌توانیم از ‍‍‍`onmouseenter/leave` نیز در این مورد استفاده کنیم، اما کمتر استفاده می‌شوند. درصورتی که از واگذاری رویدادها استفاده کنیم، کار نمی‌کنند.
2. زمانی که اشاره‌گر موس وارد یک عنصر می‌شود، سرعت آن را در `mousemove` محاسبه می‌کنیم.
3. اگر سرعت حرکت آن کند باشد، تابع `over` را صدا می‌زنیم.
4. زمانی که از عنصر خارج می‌شویم، و `over` صدا زده شده بود، تابع `out`‌ را نیز صدا می‌زنیم.

But how to measure the speed?
اما چگونه سرعت اشاره‌گر موس را اندازه گیری کنیم؟

The first idea can be: run a function every `100ms` and measure the distance between previous and new coordinates. If it's small, then the speed is small.
اولین چیزی که به ذهن می‌رسد این است که: یک تابع را هر `‍100ms‍` اجرا کنیم و مسافتی که بین مختصات قبلی و جدید طی شده را اندازه بگیریم. اگر کوچک باشد، پس سرعت حرکت اشاره‌گر موس کند بوده.

Unfortunately, there's no way to get "current mouse coordinates" in JavaScript. There's no function like `getCurrentMouseCoordinates()`.
متاسفانه راهی برای گرفتن "مختصات فعلی اشاره‌گر موس" در جاوا اسکریپت وجود ندارد. هیچ تابع از قبل آماده‌ای مانند `getCurrentMouseCoordiantes()` وجود ندارد.

The only way to get coordinates is to listen for mouse events, like `mousemove`, and take coordinates from the event object.
تنها راه برای گرفتن مختصات گوش دادن به رویدادهای موس مانند `mousemove` و گرفتن مختصات از شئ event خواهد بود.

So let's set a handler on `mousemove` to track coordinates and remember them. And then compare them, once per `100ms`.
درنتیجه باید برای رویداد `mousemove` یک کنترل‌کننده تعریف می‌کنیم تا مختصات را ذخیره کنیم، و آنها را هر `100ms` مقایسه کنیم.

P.S. Please note: the solution tests use `dispatchEvent` to see if the tooltip works right.
پی‌نوشت: توجه کنید که برای آزمایش راه حل از `dispatchEvent` استفاده می‌شود تا ببیند تولتیپ به درستی کار می‌کند یا نه.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
class HoverIntent {

constructor({
sensitivity = 0.1, // speed less than 0.1px/ms means "hovering over an element"
interval = 100, // measure mouse speed once per 100ms
sensitivity = 0.1, // سرعتی کمتر از 0.1px/ms به این معنی است که "اشاره‌گر موس روی عنصر قرار گرفته"
interval = 100, // سرعت موس هر 100ms یکبار اندازه‌گیری می‌شود
elem,
over,
out
Expand All @@ -15,12 +15,12 @@ class HoverIntent {
this.over = over;
this.out = out;

// make sure "this" is the object in event handlers.
// مطمئن شویم که "this" در کنترل‌کننده‌های رویدادها وجود خواهد داشت
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);

// and in time-measuring function (called from setInterval)
// و همچنین در تابع اندازه‌گیری زمان (که درون setInterval صدا زده می‌شود)
this.trackSpeed = this.trackSpeed.bind(this);

elem.addEventListener("mouseover", this.onMouseOver);
Expand Down Expand Up @@ -52,13 +52,13 @@ class HoverIntent {
}

onMouseOut(event) {
// if left the element
// اگر اشاره‌گر موس عنصر را ترک کند
if (!event.relatedTarget || !elem.contains(event.relatedTarget)) {
this.isOverElement = false;
this.elem.removeEventListener('mousemove', this.onMouseMove);
clearInterval(this.checkSpeedInterval);
if (this.isHover) {
// if there was a stop over the element
// اگر روی عنصر یک توقف وجود داشته باشد
this.out.call(this.elem, event);
this.isHover = false;
}
Expand All @@ -76,7 +76,7 @@ class HoverIntent {
let speed;

if (!this.lastTime || this.lastTime == this.prevTime) {
// cursor didn't move
// اشاره‌گر حرکتی نداشته
speed = 0;
} else {
speed = Math.sqrt(
Expand All @@ -90,7 +90,7 @@ class HoverIntent {
this.isHover = true;
this.over.call(this.elem, event);
} else {
// speed fast, remember new coordinates as the previous ones
// سرعت بالا، مختصات فعلی را برای اندازه‌گیری بعدی ذخیره می‌کنیم
this.prevX = this.lastX;
this.prevY = this.lastY;
this.prevTime = this.lastTime;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<span class="seconds">00</span>
</div>

<div id="tooltip" hidden>Tooltip</div>
<div id="tooltip" hidden>تولتیپ</div>

<script>
new HoverIntent({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

// Here's a brief sketch of the class
// with things that you'll need anyway
// طرح کوتاهی از کلاسی که باید بنویسید
// با چیزهایی که به هر حال به آنها نیاز پیدا خواهید کرد
class HoverIntent {

constructor({
sensitivity = 0.1, // speed less than 0.1px/ms means "hovering over an element"
interval = 100, // measure mouse speed once per 100ms: calculate the distance between previous and next points
sensitivity = 0.1, // سرعتی کمتر از 0.1px/ms به این معنی است که "اشاره‌گر موس روی عنصر قرار گرفته"
interval = 100, // سرعت اشاره‌گر موس هر 100ms یکبار چک می‌شود: مسافت طی شده بین مختصات قبلی و کنونی محسابه شود
elem,
over,
out
Expand All @@ -17,16 +17,16 @@ class HoverIntent {
this.over = over;
this.out = out;

// make sure "this" is the object in event handlers.
// مطمئن شویم که "this" در کنترل‌کننده‌های رویدادها وجود خواهد داشت
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);

// assign the handlers
// کنترل‌کننده‌ها را به عنصر متصل می‌کنیم
elem.addEventListener("mouseover", this.onMouseOver);
elem.addEventListener("mouseout", this.onMouseOut);

// continue from this point
// از اینجا شروع کنید

}

Expand All @@ -44,8 +44,8 @@ class HoverIntent {


destroy() {
/* your code to "disable" the functionality, remove all handlers */
/* it's needed for the tests to work */
/* کدی که برای "غیرفعال" کردن عملکرد تولتیپ می‌نویسید، همه کنترل‌کننده‌ها را از روی عنصر حذف کنید */
/* برای آزمایش راه حل شما نیاز است */
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,30 @@ importance: 5

---

# "Smart" tooltip
# تولتیپ "باهوش"

Write a function that shows a tooltip over an element only if the visitor moves the mouse *to it*, but not *through it*.
تابعی بنویسید که یک تولتیپ را فقط در صورتی نمایش دهد که کاربر اشاره‌گر موس را *داخل آن* ببرد و نه اینکه از روی آن *فقط عبور* کند.

In other words, if the visitor moves the mouse to the element and stops there -- show the tooltip. And if they just moved the mouse through, then no need, who wants extra blinking?
به عبارت دیگر، اگر که کاربر اشاره‌گر موس را روی عنصر ببرد و آنجا متوقف شود، تولتیپ نمایش داده شود. و اگر موس از روی عنصر عبور کرد، نیازی به نمایش تولتیپ نیست. چه کسی یک به تولتیپ لحظه‌ای نیاز دارد؟

Technically, we can measure the mouse speed over the element, and if it's slow then we assume that it comes "over the element" and show the tooltip, if it's fast -- then we ignore it.
از نظر فنی، می‌توانیم سرعت اشاره‌گر موس را روی عنصر اندازه بگیریم، و اگر آرام باشد فرض کنیم که اشاره‌گر "روی عنصر قرار گرفته" و تولتیپ را نمایش دهیم. اگر سریع بود، آنرا نادیده بگیریم.

Make a universal object `new HoverIntent(options)` for it.
برای این منظور یک شئ گلوبال به صورت `new HoverIntent(options)` بسازید.

Its `options`:
- `elem` -- element to track.
- `over` -- a function to call if the mouse came to the element: that is, it moves slowly or stopped over it.
- `out` -- a function to call when the mouse leaves the element (if `over` was called).
خصوصیات `options`:
- `elem` -- عنصری که می‌خواهیم حرکت اشاره‌گر را روی آن کنترل کنیم.
- `over` -- تابعی که در صورت "قرار گرفتن اشاره‌گر روی عنصر" صدا زده می‌شود: که یعنی حرکت اشاره‌گر موس کند بوده، یا روی عنصر توقف کرده.
- `out` -- تابعی که زمانی اشاره‌گر موس عنصر را ترک می‌کند صدا زده‌ می‌شود. (اگر تابع `over` صدا زده شده باشد).

An example of using such object for the tooltip:
یک مثال از چگونگی استفاده از چنین شئ برای تولتیپ این چنین خواهد بود:

```js
// a sample tooltip
// تولتیپ نمونه
let tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Tooltip";

// the object will track mouse and call over/out
// این شئ حرکت اشاره‌گر موس را دنبال و توابع over/out را صدا می‌زند.
new HoverIntent({
elem,
over() {
Expand All @@ -39,10 +39,10 @@ new HoverIntent({
});
```

The demo:
دمو:

[iframe src="solution" height=140]

If you move the mouse over the "clock" fast then nothing happens, and if you do it slow or stop on them, then there will be a tooltip.
اگر که اشاره‌گر موس را سریعا از روی "ساعت" حرکت دهید، اتفاقی نمی‌افتد، اگر چه در صورتی که حرکت اشاره‌گر موس آهسته باشد یا روی آن توقف کنید، یک تولتیپ نمایش داده می‌شود.

Please note: the tooltip doesn't "blink" when the cursor moves between the clock subelements.
توجه: تولتیپ نباید هنگامی که اشاره‌گر روی فرزندان ساعت حرکت می‌کند رفتار "چشمک زن" داشته باشد.
Loading