Skip to content

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

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 2 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 */
/* El tooltip debe seguir funcionando aúnn después de hacer scroll */
}

.tooltip {
Expand Down Expand Up @@ -66,25 +66,25 @@
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.
// Importante: un movimiento rápido del mouse podría saltar directamente al descendiente en un nodo anotado, ignorando el padre
// por lo tanto mouseover podría ejecutarse en un descendiente.

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

if (!anchorElem) return;

// show tooltip and remember it
// Mostrando el tooltip y recordándolo.
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
// Es posible que mouseout se active, pero estaremos dentro del elemento
// (el target ocurrirá dentro y se aparecerá)
// pero en este caso tendremos un mouse over inmediato,
// entonces el tooltip será destruido y mostrado otra vez.
//
// luckily, the "blinking" won't be visible,
// as both events happen almost at the same time
// Afortunadamente, el parpadeo no es visble,
// ya que ambos eventos ocurren casi al mismo tiempo.
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
// Posicionando el tooltip sobre el centro del elemento
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 @@ -6,7 +6,7 @@
<style>
body {
height: 2000px;
/* the tooltip should work after page scroll too */
/* El tooltip debe funcionar también despues de hacer scroll */
}

.tooltip {
Expand Down 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="Aquí esta el interior de la casa" id="house">
<div data-tooltip="Aquí está el techo" 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>
<p>Soon they met a man. <a href="https://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Continúa leyendo…">Colócate sobre mí</a></p>

</div>

<script>
// ...your code...
// ...tu código...
</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
# Comportamiento mejorado de un tooltip

Write JavaScript that shows a tooltip over an element with the attribute `data-tooltip`. The value of this attribute should become the tooltip text.
Escribe JavaScript que muestre un tooltip sobre un elemento con el atributo `data-tooltip`. El valor de este atributo debe convertirse en el texto del 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.
Es como la tarea <info:task/behavior-tooltip>, pero aquí los elementos anotados se pueden anidar. Los tooltips más internos se muestran.

Only one tooltip may show up at the same time.
Solamente un tooltip puede aparecer a la vez.

For instance:
Por ejemplo:

```html
<div data-tooltip="Hereis the house interior" id="house">
<div data-tooltip="Hereis the roof" id="roof"></div>
<div data-tooltip="Aquíestá el interior de la casa" id="house">
<div data-tooltip="Aquíestá el techo" 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://en.wikipedia.org/wiki/The_Three_Little_Pigs" data-tooltip="Continúa leyendo…">Colócate sobre mi</a>
</div>
```

The result in iframe:
El resultado en el 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`.
El algorítmo luce simple:
1. Coloca los controladores `onmouseover/out` en el elemento. Aquí también podemos usar `onmouseenter/leave` pero son menos universales, no funcionan si introducimos delegaciones.
2. Cuando el cursor ingrese al elemento debes medir la velocidad en `mousemove`.
3. Si la velocidad es lenta hay que ejecutar `over`.
4. Si estamos saliendo del elemento, y `over` ya se había ejecutado, ahora ejecutamos `out`.

But how to measure the speed?
¿Pero cómo mediremos la velocidad?

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.
La primera idea puede ser: correr una función cada `100ms` y medir la distancia entre la coordenada anterior y la actual. Si es pequeña entonces la velocidad fue rápida.

Unfortunately, there's no way to get "current mouse coordinates" in JavaScript. There's no function like `getCurrentMouseCoordinates()`.
Desafortunadamente no hay manera para obtener las coordenadas actuales del mouse en JavaScript. No existe algo así como `getCurrentMouseCoordinates()`.

The only way to get coordinates is to listen for mouse events, like `mousemove`, and take coordinates from the event object.
La única manera es registrando los eventos del mouse, como `mousemove`, y tomar las coordenadas del objeto del evento.

So let's set a handler on `mousemove` to track coordinates and remember them. And then compare them, once per `100ms`.
Entonces configuremos un `mousemove` para registrar las coordenadas y recordarlas. Y entonces las comparamos, una por cada `100ms`.

P.S. Please note: the solution tests use `dispatchEvent` to see if the tooltip works right.
PD. Toma nota: El test de la solución usa `dispatchEvent` para ver si el tooltip funciona bien.
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, // Velocidad menor a 0.1px/ms supone un "posicionamiento sobre el elemento"
interval = 100, // Medida de la velocidad del mouse una vez por cada 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.
// Segurándonos de que "this" es el objeto en los controladores de eventos
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)
// y en la función para medir el tiempo (llamada desde setInterval)
this.trackSpeed = this.trackSpeed.bind(this);

elem.addEventListener("mouseover", this.onMouseOver);
Expand All @@ -32,16 +32,16 @@ class HoverIntent {
onMouseOver(event) {

if (this.isOverElement) {
// if we're over the element, then ignore the event
// we are already measuring the speed
// Si estamos sobre el elemento ignoramos el evento
// Ya estamos midiendo la velocidad
return;
}

this.isOverElement = true;

// after every mousemove we'll be check the distance
// between the previous and the current mouse coordinates
// if it's less than sensivity, then the speed is slow
//Después de cada mousemove vamos a checar la distancia
// entre la coordinada previa y actual del mouse
// si es menor que sensivity entonces la velocidad es muy rápida

this.prevX = event.pageX;
this.prevY = event.pageY;
Expand All @@ -52,13 +52,13 @@ class HoverIntent {
}

onMouseOut(event) {
// if left the element
// Si abandomanos el elemento
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
// Si nos detenemos sobre el elemento
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
// Cursor sin movimieto (detenido)
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
// Hubo movimiento rápido, registramos las coordenadas actuales como las anteriores
this.prevX = this.lastX;
this.prevY = this.lastY;
this.prevTime = this.lastTime;
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
// Aquí hay un breve sketch con la clase
// de cosas que vas a ocupar de cualquier forma
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, // Velocidad menor a 0.1px/ms supone un "posicionamiento sobre el elemento"
interval = 100, // Medida de la velocidad del mouse una vez por cada 100ms: cualcula la distancia entre el punto anterior y el actual
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.
// // Segurándonos de que "this" es el objeto en los controladores de eventos
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);

// assign the handlers
// Asignando los controladores
elem.addEventListener("mouseover", this.onMouseOver);
elem.addEventListener("mouseout", this.onMouseOut);

// continue from this point
// Continua trabajando desde este punto

}

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 */
/* Tu código para "deshabilitar" la funcionalidad, remueve los controladores */
/* Es necesario para que las pruebas funcionen*/
}

}
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
importance: 5
Importance: 5

---

# "Smart" tooltip
# Tooltip "inteligente"

Write a function that shows a tooltip over an element only if the visitor moves the mouse *to it*, but not *through it*.
Escribe una función que muestre un tooltip sobre un elemento solamente si el visitante mueve el mouse *hacia él*, pero no *a través de él*.

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?
En otras palabras, si el visitante mueve el mouse hacia el elemento y para ahí -- muestra el tooltip. Y si solamente mueve el mouse a través, entonces no lo necesitamos. ¿Quién quiere parpadeos extra?

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.
Tecnicamente, podemos medir la velocidad del mouse sobre el elemento, y si es lenta podemos asumir que el mouse viene "sobre el elemento" y mostramos el tooltip, si es rápida -- entonces lo ignoramos.

Make a universal object `new HoverIntent(options)` for it.
Hay que crear un objeto universal `new HoverIntent(options)` para ello.

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).
Sus `options`:
- `elem` -- elemento a seguir.
- `over` -- una función a llamar si el el mouse viene hacia el elemento: o sea, si viene lentamente o para sobre él.
- `out` -- una función a llmar cuando el mouse abandona el lemento (si `over` fue llamado).

An example of using such object for the tooltip:
Un ejemplo de dicho objeto siendo usado para el tooltip:

```js
// a sample tooltip
// Un tooltip de muestra
let tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Tooltip";

// the object will track mouse and call over/out
// El objeto va a rastrear al mouse y llamar a over/out
new HoverIntent({
elem,
over() {
Expand All @@ -39,10 +39,10 @@ new HoverIntent({
});
```

The demo:
El 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.
Si mueves el mouse sobre el "reloj" rápido no pasará nada, y si lo haces lento o paras sobre él entonces habrá un tooltip.

Please note: the tooltip doesn't "blink" when the cursor moves between the clock subelements.
Toma en cuenta que el tooltip no "parpadea" cuando el cusor se mueve entre subelementos del reloj.
Loading