◐ Shell
reader mode source ↗
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
File filter
Conversations
Jump to
Diff view
Apply and reload
Show whitespace
Diff view
Apply and reload
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 @@ -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>

<p>Once upon a time there was a mother pig who had three little pigs.</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>

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

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

<p>Once upon a time there was a mother pig who had three little pigs.</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>

</div>

<script>
// ...your code...
</script>

</body>
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.

That's like the task <info:task/behavior-tooltip>, but here the annotated elements can be nested. The most deeply nested tooltip is shown.

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>
...
<a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Read on…">Hover over me</a>
</div>
```

The result in 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`.

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.

Unfortunately, there's no way to get "current mouse coordinates" in JavaScript. There's no function like `getCurrentMouseCoordinates()`.

The only way to get coordinates is to listen for mouse events, like `mousemove`, and take coordinates from the event object.

So let's set a handler on `mousemove` to track coordinates and remember them. And then compare them, once per `100ms`.

P.S. Please note: the solution tests use `dispatchEvent` to see if the tooltip works right.
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
elem,
over,
out
@@ -15,12 +15,12 @@ class HoverIntent {
this.over = over;
this.out = out;

// make sure "this" is the object in event handlers.
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)
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;
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
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.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.

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

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
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
Toggle all file notes Toggle all file annotations