renovations

This commit is contained in:
Ilya Kantor 2015-02-13 18:33:55 +03:00
parent a5e8c1219f
commit 41f07253d3
21 changed files with 536 additions and 126 deletions

View file

@ -128,15 +128,16 @@ alert( +"2" > +"14" ); // false, теперь правильно
## Сравнение разных типов ## Сравнение разных типов
При сравнении значения преобразуются к числам. Исключение: когда оба значения -- строки, тогда не преобразуются. При сравнении значений разных типов, используется числовое преобразование. Оно применяется к обоим значениям.
Например: Например:
```js ```js
//+ run //+ run
alert( '2' > 1 ); // true alert( '2' > 1 ); // true, сравнивается как 2 > 1
alert( '01' == 1 ); //true alert( '01' == 1 ); // true, сравнивается как 1 == 1
alert( false == 0 ); // true, false становится 0, а true 1. alert( false == 0 ); // true, false становится числом 0
alert( true == 1); // true, так как true становится числом 1.
``` ```
Тема преобразований типов будет продолжена далее, в главе [](/types-conversion). Тема преобразований типов будет продолжена далее, в главе [](/types-conversion).
@ -177,12 +178,14 @@ alert(0 === false); // false, т.к. типы различны
Проблемы со специальными значениями возможны, когда к переменной применяется операция сравнения `> < <= >=`, а у неё может быть как численное значение, так и `null/undefined`. Проблемы со специальными значениями возможны, когда к переменной применяется операция сравнения `> < <= >=`, а у неё может быть как численное значение, так и `null/undefined`.
**Интуитивно кажется, что `null/undefined` эквивалентны нулю, но это не так! Они ведут себя по-другому.** **Интуитивно кажется, что `null/undefined` эквивалентны нулю, но это не так.**
Они ведут себя по-другому.
<ol> <ol>
<li>**Значения `null` и `undefined` равны `==` друг другу и не равны чему бы то ни было ещё.** <li>Значения `null` и `undefined` равны `==` друг другу и не равны чему бы то ни было ещё.
Это жёсткое правило буквально прописано в спецификации языка.</li> Это жёсткое правило буквально прописано в спецификации языка.</li>
<li>**При преобразовании в число `null` становится `0`, а `undefined` становится `NaN`.**</li> <li>При преобразовании в число `null` становится `0`, а `undefined` становится `NaN`.</li>
</ol> </ol>
Посмотрим забавные следствия. Посмотрим забавные следствия.
@ -240,4 +243,6 @@ alert(undefined == 0); // false (3)
<li>Строки сравниваются побуквенно.</li> <li>Строки сравниваются побуквенно.</li>
<li>Значения разных типов приводятся к числу при сравнении, за исключением строгого равенства `===` (`!==`).</li> <li>Значения разных типов приводятся к числу при сравнении, за исключением строгого равенства `===` (`!==`).</li>
<li>Значения `null` и `undefined` равны `==` друг другу и не равны ничему другому. В других сравнениях (с участием `>`,`<`) их лучше не использовать, так как они ведут себя не как `0`.</li> <li>Значения `null` и `undefined` равны `==` друг другу и не равны ничему другому. В других сравнениях (с участием `>`,`<`) их лучше не использовать, так как они ведут себя не как `0`.</li>
</ul> </ul>
Мы ещё вернёмся к теме сравнения позже, когда лучше изучим различные типы данных в JavaScript.

View file

@ -8,7 +8,7 @@
} }
.tooltip { .tooltip {
position:absolute; position: fixed;
z-index:100; /* подсказка должна перекрывать другие элементы */ z-index:100; /* подсказка должна перекрывать другие элементы */
padding: 10px 20px; padding: 10px 20px;
@ -66,8 +66,7 @@
var showingTooltip; var showingTooltip;
document.onmouseover = function(e) { document.onmouseover = function(e) {
e = e || event; var target = e.target;
var target = e.target || e.srcElement;
// ВАЖНО: mouseover может сработать сразу на потомке // ВАЖНО: mouseover может сработать сразу на потомке
// минуя родителя (при быстром движении мышью) // минуя родителя (при быстром движении мышью)
@ -105,16 +104,14 @@ function showTooltip(text, elem) {
tooltipElem.innerHTML = text; tooltipElem.innerHTML = text;
document.body.appendChild(tooltipElem); document.body.appendChild(tooltipElem);
var coords = getCoords(elem); var coords = elem.getBoundingClientRect();
// не вылезаем за пределы экрана var left = coords.left + (elem.offsetWidth - tooltipElem.offsetWidth)/2;
var scroll = getPageScroll(); if (left < 0) left = 0; // не вылезать за левую границу экрана
var left = coords.left + (elem.offsetWidth - tooltipElem.offsetWidth)/2^0;
if (left < scroll.left) left = scroll.left; // не вылезать за левую границу экрана
// не вылезать за верхнюю границу окна
var top = coords.top - tooltipElem.offsetHeight - 5; var top = coords.top - tooltipElem.offsetHeight - 5;
if (top < scroll.top) { if (top < 0) {
top = coords.top + elem.offsetHeight + 5; top = coords.top + elem.offsetHeight + 5;
} }

View file

@ -46,50 +46,5 @@
</div> </div>
<script>
// --------- вспомогательные функции ---------
function getCoords(elem) {
var box = elem.getBoundingClientRect();
var body = document.body;
var docEl = document.documentElement;
var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
var clientTop = docEl.clientTop || body.clientTop || 0;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
}
function getPageScroll() {
if (window.pageXOffset != undefined) {
return {
left: pageXOffset,
top: pageYOffset
}
}
var html = document.documentElement;
var body = document.body;
var top = html.scrollTop || body && body.scrollTop || 0;
top -= html.clientTop;
var left = html.scrollLeft || body && body.scrollLeft || 0;
left -= html.clientLeft;
return { top: top, left: left };
}
</script>
</body> </body>
</html> </html>

View file

@ -1,6 +1,6 @@
# Поведение "вложенная подсказка" # Поведение "вложенная подсказка"
[importance 4] [importance 5]
Напишите JS-код, который будет показывать всплывающую подсказку над элементом, если у него есть атрибут `data-tooltip`. Напишите JS-код, который будет показывать всплывающую подсказку над элементом, если у него есть атрибут `data-tooltip`.
@ -20,7 +20,6 @@
[iframe src="solution" height=300 border=1] [iframe src="solution" height=300 border=1]
Исходный документ содержит вспомогательные функции [](#getPageScroll) и [](#getCoords). Вы можете использовать как заготовку решение задачи [](/task/behavior-tooltip).
Вы также можете использовать как заготовку решение задачи [](/task/behavior-tooltip).

View file

@ -1,7 +0,0 @@
Самый простой способ решения этой задачи -- замерять скорость движения курсора. То есть, при `mousemove` вычислять расстояние между текущими координатами и предыдущими, а затем делить на разницу во времени.
Когда скорость будет очень маленькой, например 5 пикселей за 100 миллисекунд -- можно считать, что курсор остановился (некоторое дрожание может присутствовать) и обработать этот факт.
Обработчик `mousemove` может стоять на всём документе, либо на контейнере, который включает в себя интересующие нас элементы.

View file

@ -1,9 +0,0 @@
# Как отследить прекращение движения курсора?
[importance 5]
Представим ситуацию -- посетитель быстро проводит мышью над элементами и останавливается на интересном ему.
Нам надо запустить код (например открыть пункт меню) для того, на котором он остановился, а те элементы, над которыми он быстро провёл мышь, но на которых не остановился -- игнорировать, даже если события мыши на них произошли.
Как бы вы решали такую задачу?

View file

@ -0,0 +1,20 @@
Будем замерять скорость движения курсора.
Для этого можно запустить `setInterval`, который каждые 100мс (или другой интервал) будет сравнивать текущие координаты курсора с предыдущими и, если расстояние пройдено маленькое, считаем, что посетитель "навёл указатель на элемент", вызвать `options.over`.
В браузере нет способа "просто получить" текущие координаты. Это может сделать обработчик события, в данном случае `mousemove`. Поэтому нужно будет поставить обработчик на `mousemove` и при каждом движении запоминать текущие координаты, чтобы `setInterval` мог раз в 100мс сравнивать их.
Можно обойтись и без `setInterval` -- сравнивать координаты при каждом срабатывании `mousemove`. Если передвинулись на маленькое расстояние с последнего `mousemove` -- это "наведение на элемент", а на большое -- игнорируем. Вариант с `setInterval` теоретически надёжнее, но на практике и один `mousemove` работает.
Чтобы наш код не срабатывал чересчур часто, мы будем начинать анализ координат при заходе на элемент, а заканчивать -- при выходе с него.
Если выход осуществлён, и при этом на элементе зафиксировано "состояние наведения", то нужно вызвать соответствующий обработчик `options.out`.
Чтобы точно отловить момент входа и выхода, без учёта подэлементов (во избежание мигания), можно использовать `mouseenter/mouseleave`.
В решении, предложенном ниже, однако, используется `mouseover/mouseout`, так как это позволит легко "прикрутить" к такому объекту делегирование, если потребуется. А, чтобы не было лишних срабатываний, лишние переходы отфильтровываются.

View file

@ -0,0 +1,77 @@
function HoverIntent(options) {
options = Object.create(options); // not to modify the object
options.interval = options.interval || 100;
// скорость меньше 1px/ms считается остановкой над элементом
options.sensitivity = options.sensitivity || 0.1;
var elem = options.elem;
// instantiate variables
// cX, cY = current X and Y position of mouse, updated by mousemove event
// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
var cX, cY, pX, pY, cTime, pTime;
var checkSpeedInterval;
var isOverElement;
var isHover;
// A private function for handling mouse 'hovering'
elem.addEventListener("mouseover", function(event) {
if (isOverElement) {
// если мы и так над элементом, то это всплывший переход внутри него
// мы и так уже замеряем скорость, поэтому этот переход лишний
return;
}
isOverElement = true;
// при каждом движении мыши mousemove мы будем вычислять расстояние между
// предыдущими и текущими координатами курсора
// если оно меньше sensivity, то скорость маленькая и это наведение на элемент
// pX, pY - "предыдущие" координаты
pX = event.pageX;
pY = event.pageY;
pTime = Date.now();
elem.addEventListener('mousemove', onMouseMove);
});
elem.addEventListener("mouseout", function(event) {
// если ушли вовне элемента
if (event.relatedTarget && !elem.contains(event.relatedTarget)) {
isOverElement = false;
elem.removeEventListener('mousemove', onMouseMove);
if (isHover) {
// если была остановка над элементом
options.out.call(elem, event);
isHover = false;
}
}
});
function onMouseMove(event) {
cX = event.pageX;
cY = event.pageY;
cTime = Date.now();
if (pTime == cTime) return; // когда mousemove вместе с mouseover
var speed = Math.sqrt(Math.pow(pX - cX, 2) + Math.pow(pY - cY, 2)) / (cTime - pTime);
if (speed < options.sensitivity) {
// если с предыдущей позиции меньше sensivity дистанция, то "остановка на элементе"
elem.removeEventListener("mousemove", onMouseMove);
isHover = true;
options.over.call(elem, event);
} else {
// следующее измерение с текущей точки
pX = cX;
pY = cY;
pTime = cTime;
}
}
}

View file

@ -0,0 +1,44 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script src="hoverIntent.js"></script>
</head>
<body>
<div id="elem" class="clock">
<span class="hours">12</span>
:
<span class="minutes">30</span>
:
<span class="seconds">00</span>
</div>
<script>
var tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Подсказка";
new HoverIntent({
elem: elem,
over: function() {
tooltip.style.left = this.getBoundingClientRect().left + 'px';
tooltip.style.top = this.getBoundingClientRect().bottom + 5 + 'px';
document.body.appendChild(tooltip);
},
out: function() {
document.body.removeChild(tooltip);
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
.hours {
color: red;
}
.minutes {
color: green;
}
.seconds {
color: blue;
}
.clock {
border: 1px dashed black;
padding: 5px;
display: inline-block;
margin: 30px;
background: yellow;
}
.tooltip {
position: absolute;
background: #eee;
border: 1px brown solid;
padding: 3px;
}

View file

@ -0,0 +1,5 @@
function HoverIntent(options) {
/* ваш код */
}

View file

@ -0,0 +1,44 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script src="hoverIntent.js"></script>
</head>
<body>
<div id="elem" class="clock">
<span class="hours">12</span>
:
<span class="minutes">30</span>
:
<span class="seconds">00</span>
</div>
<script>
var tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Подсказка";
new HoverIntent({
elem: elem,
over: function() {
tooltip.style.left = this.getBoundingClientRect().left + 'px';
tooltip.style.top = this.getBoundingClientRect().bottom + 5 + 'px';
document.body.appendChild(tooltip);
},
out: function() {
document.body.removeChild(tooltip);
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
.hours {
color: red;
}
.minutes {
color: green;
}
.seconds {
color: blue;
}
.clock {
border: 1px dashed black;
padding: 5px;
display: inline-block;
margin: 30px;
background: yellow;
}
.tooltip {
position: absolute;
background: #eee;
border: 1px brown solid;
padding: 3px;
}

View file

@ -0,0 +1,53 @@
# Подсказка при замедлении над элементом
[importance 5]
Нужно написать функцию, которая показывает подсказку при *наведении* на элемент, но не при *быстром проходе* над ним.
То есть, если посетитель именно навёл курсор мыши на элемент и почти остановился -- подсказку показать, а если быстро провёл над ним, то не надо (зачем излишнее мигание?).
Технически -- можно измерять скорость движения мыши над элементом, если она маленькая, то считаем, что это "наведение на элемент" (показать подсказку), если большая -- "быстрый проход мимо элемента" (не показывать).
Задача -- сделать универсальный код, который отслеживает "наведение на элемент".
Пусть это будет объект `new HoverIntent(options)`, который при создании принимает `options`:
<ul>
<li>`elem` -- элемент, наведение на который нужно отслеживать.</li>
<li>`over` -- функция-обработчик наведения на элемент.</li>
<li>`out` -- функция-обработчик ухода с элемента (если было наведение).</li>
</ul>
Пример использования такого объекта для подсказки:
```js
function HoverIntent(options) {
//... ваш код ...
}
// образец подсказки
var tooltip = document.createElement('div');
tooltip.className = "tooltip";
tooltip.innerHTML = "Подсказка";
// при "наведении на элемент" показать подсказку
new HoverIntent({
elem: elem,
over: function() {
tooltip.style.left = this.getBoundingClientRect().left + 'px';
tooltip.style.top = this.getBoundingClientRect().bottom + 5 + 'px';
document.body.appendChild(tooltip);
},
out: function() {
document.body.removeChild(tooltip);
}
});
```
Демо:
[iframe src="solution" height=110]
Если провести мышкой над "часиками" быстро, то ничего не будет, а если медленно или остановиться на них, то появится подсказка.
Обратите внимание -- подсказка не "мигает"" при проходе мыши внутри "часиков", по подэлементам.

View file

@ -1,36 +1,65 @@
# Мышь: движение mouseover/out, mouseenter/leave # Мышь: движение mouseover/out, mouseenter/leave
В этой главе мы рассмотрим события, возникающие при движении мыши над элементами. В этой главе мы рассмотрим события, возникающие при движении мыши над элементами.
[cut] [cut]
## События mouseover/mouseout, свойство relatedTarget ## События mouseover/mouseout, свойство relatedTarget
Событие `mouseover` происходит, когда мышь появляется над элементом, а `mouseout` -- когда уходит из него. Событие `mouseover` происходит, когда мышь появляется над элементом, а `mouseout` -- когда уходит из него.
При этом мы можем узнать, с какого элемента пришла (или на какой ушла) мышь, используя дополнительное свойство `relatedTarget`. <img src="mouseover-mouseout.svg">
В случае `mouseover` оно содержит элемент, *с которого* пришла мышь, а для `mouseout` -- *на который* ушла. При этом мы можем узнать, с какого элемента пришла (или на какой ушла) мышь, используя дополнительное свойство объекта события `relatedTarget`.
Например, в обработчике события `mouseover`:
<ul>
<li>`event.target` -- элемент, на который пришла мышь, то есть на котором возникло событие.</li>
<li>`event.relatedTarget` -- элемент, с которого пришла мышь.</li>
</ul>
Для `mouseout` всё наоборот:
<ul>
<li>`event.target` -- элемент, с которого ушла мышь, то есть на котором возникло событие.</li>
<li>`event.relatedTarget` -- элемент, на который перешла мышь.</li>
</ul>
[online]
В примере ниже вы можете наглядно посмотреть события `mouseover/out`, возникающие на всех элементах. В примере ниже вы можете наглядно посмотреть события `mouseover/out`, возникающие на всех элементах.
[codetabs src="mouseoverout" height=220] [codetabs src="mouseoverout" height=220]
[/online]
Можно заметить, что в некоторых случаях значение `relatedTarget` может быть `null`. Это вполне нормально и означает, что мышь пришла из-за пределов окна (или ушла за окно). [warn header="`relatedTarget` может быть `null`"]
Свойство `relatedTarget` может быть равно `null`.
## Частота событий mousemove и mouseover/out Это вполне нормально и означает, что мышь пришла не с другого элемента, а из-за пределов окна (или ушла за окно). Мы обязательно должны иметь в виду такую возможность, когда пишем код, который обращается к свойствам `event.relatedTarget`.
[/warn]
## Частота событий
Событие `mousemove` срабатывает при передвижении мыши. Но это не значит, что каждый пиксель экрана порождает отдельное событие! Событие `mousemove` срабатывает при передвижении мыши. Но это не значит, что каждый пиксель экрана порождает отдельное событие!
События `mousemove` и `mouseover/mouseout` срабатывают так часто, насколько это позволяет внутренняя система взаимодействия с мышью браузера. События `mousemove` и `mouseover/mouseout` срабатывают так часто, насколько это позволяет внутренняя система взаимодействия с мышью браузера.
Это означает, что если вы двигаете мышью очень быстро, то DOM-элементы, через которые мышь проходит на большой скорости, могут быть пропущены. Это означает, что если посетитель двигает мышью быстро, то DOM-элементы, через которые мышь проходит на большой скорости, могут быть пропущены.
К примеру: <img src="mouseover-mouseout-over-elems.svg">
<ul>
<li>Курсор может быстро перейти *над* родительским элементом в дочерний, при этом *не вызвав событий на родителе*, как будто он через родителя никогда не проходил.</li>
<li>Курсор может быстро выйти из дочернего элемента без генерации событий на родителе.</li>
</ul>
При быстром движении с элемента `#FROM` до элемента `#TO`, как изображено на картинке выше -- промежуточные `<DIV>` будут пропущены. Сработает только событие `mouseout` на `#FROM` и `mouseover` на `#TO`.
На практике это полезно, потому что таких промежуточных элементов может быть много, и если обрабатывать заход и уход с каждого -- дополнительные вычислительные затраты.
С другой стороны, мы должны это понимать и не рассчитывать на то, что мышь аккуратно пройдёт с одного элемента на другой и так далее. Нет, она "прыгает".
В частности, возможна ситуация, когда курсор прыгает в середину страницы, и при этом `relatedTarget=null`, то есть он пришёл "ниоткуда" (на самом деле извне окна):
<img src="mouseover-mouseout-from-outside.svg">
Обратим внимание ещё на такую деталь. При быстром движении курсор окажется над `#TO` сразу, даже если этот элемент глубоко в DOM. Его родители при движении сквозь них события не поймают.
[online]
Попробуйте увидеть это "вживую" на тестовом стенде ниже. Попробуйте увидеть это "вживую" на тестовом стенде ниже.
Его HTML представляет собой два вложенных `div'а`. Его HTML представляет собой два вложенных `div'а`.
@ -40,22 +69,44 @@
А еще попробуйте зайти курсором мыши на красный `div` и потом быстро вывести мышь из него куда-нибудь сквозь зеленый. Если движение мыши достаточно быстрое, то родительский элемент будет проигнорирован. А еще попробуйте зайти курсором мыши на красный `div` и потом быстро вывести мышь из него куда-нибудь сквозь зеленый. Если движение мыши достаточно быстрое, то родительский элемент будет проигнорирован.
[codetabs height=360 src="mouseoverout-fast"] [codetabs height=360 src="mouseoverout-fast"]
[/online]
Важно иметь в виду эту особенность событий, чтобы не написать код, который рассчитан на последовательный проход над элементами. В остальном это вполне удобно.
Важно иметь в виду эту особенность событий, чтобы не написать код, который рассчитан на последовательный проход над элементами.
## "Лишний" mouseout при уходе на потомка ## "Лишний" mouseout при уходе на потомка
Представьте ситуацию -- курсор зашёл на элемент. Сработал `mouseover` на нём. Потом курсор идёт на дочерний... И, оказывается, на элементе-родителе при этом происходит `mouseout`! Как будто курсор с него ушёл, хотя он всего лишь перешёл на потомка. Представьте ситуацию -- курсор зашёл на элемент. Сработал `mouseover` на нём. Потом курсор идёт на дочерний... И, оказывается, на элементе-родителе при этом происходит `mouseout`! Как будто курсор с него ушёл, хотя он всего лишь перешёл на потомка.
**При переходе на потомка срабатывает `mouseout` на родителе.** **При переходе на потомка срабатывает `mouseout` на родителе.**
<img src="mouseover-to-child.svg">
Это кажется странным, но легко объяснимо. Это кажется странным, но легко объяснимо.
**Согласно браузерной логике, курсор мыши может быть только над *одним* элементом -- самым глубоким в DOM (и верхним по z-index).** **Согласно браузерной логике, курсор мыши может быть только над *одним* элементом -- самым глубоким в DOM (и верхним по z-index).**
Так что если он перешел куда-нибудь, то автоматически ушёл с предыдущего элемента. Всё просто. Так что если он перешел куда-нибудь, то автоматически ушёл с предыдущего элемента. Всё просто.
К чему это приводит на практике, можно увидеть в примере ниже. В нём красный `div` вложен в синий. На синем стоит обработчик, который записывает его `mouseover/mouseout`. Самое забавное начинается чуть позже.
Ведь события `mouseover` и `mouseout` всплывают.
Получается, что если поставить обработчики `mouseover` и `mouseout` на `#FROM` и `#TO`, то последовательность срабатывания при переходе `#FROM` -> `#TO` будет следующей:
<ol>
<li>`mouseout` на `#FROM` (с `event.target=#FROM`, `event.relatedTarget=#TO`).</li>
<li>`mouseover` на `#TO` (с `event.target=#TO`, `event.relatedTarget=#FROM`).</li>
<li>Событие `mouseover` после срабатывания на `#TO` всплывает выше, запуская обработчики `mouseover` на родителях. Ближайший родитель -- как раз `#FROM`, то есть сработает обработчик `mouseover` на нём, с теми же значениями `target/relatedTarget`.</li>
</ol>
Если посмотреть на `1)` и `3)`, то видно, что то видно, что на `#FROM` сработает сначала `mouseout`, а затем с `#TO` всплывёт `mouseover`.
Если по `mouseover` мы что-то показываем, а по `mouseout` -- скрываем, то может получается "мигание".
**У обработчиков создаётся впечатление, что курсор ушёл `mouseout` с родителя, а затем тут же перешёл `mouseover` на него (за счёт всплытия `mouseover` с потомка).**
[online]
Это можно увидеть в примере ниже. В нём красный `div` вложен в синий. На синем стоит обработчик, который записывает его `mouseover/mouseout`.
Зайдите на синий элемент, а потом переведите мышь на красный -- и наблюдайте за событиями: Зайдите на синий элемент, а потом переведите мышь на красный -- и наблюдайте за событиями:
@ -68,12 +119,9 @@
</ol> </ol>
На самом деле, обратного перехода нет. Событие `mouseover` сработало на потомке (видно по `target: red`), а затем всплыло. На самом деле, обратного перехода нет. Событие `mouseover` сработало на потомке (видно по `target: red`), а затем всплыло.
[/online]
**То есть, у кода создаётся впечатление, что курсор ушёл `mouseout` с родителя, а затем тут же перешёл `mouseover` на него (за счёт всплытия `mouseover` с потомка).** Если действия при наведении и уходе курсора с родителя простые, например скрытие/показ подсказки, то можно вообще ничего не заметить. Ведь события происходят сразу одно за другим, подсказка будет скрыта по `mouseout` и тут же показана по `mouseover`.
Как это влияет на его поведение?
Если действия при наведении и уходе курсора с родителя простые, например скрытие/показ подсказки, то можно вообще ничего не заметить. Ведь события происходят одновременно, подсказка будет скрыта по `mouseout` и тут же показана по `mouseover`.
Если же происходит что-то более сложное, то бывает важно отследить момент "настоящего" ухода, то есть понять, когда элемент зашёл на родителя, а когда ушёл -- без учёта переходов по дочерним элементам. Если же происходит что-то более сложное, то бывает важно отследить момент "настоящего" ухода, то есть понять, когда элемент зашёл на родителя, а когда ушёл -- без учёта переходов по дочерним элементам.
@ -84,39 +132,34 @@
События `mouseenter/mouseleave` похожи на `mouseover/mouseout`. Они тоже срабатывают, когда курсор заходит на элемент и уходит с него, но с двумя отличиями. События `mouseenter/mouseleave` похожи на `mouseover/mouseout`. Они тоже срабатывают, когда курсор заходит на элемент и уходит с него, но с двумя отличиями.
<ol> <ol>
<li>При переходе на потомка курсор не уходит с родителя.</li> <li>Не учитываются переходы внутри элемента.</li>
<li>События `mouseenter/mouseleave` не всплывают.</li> <li>События `mouseenter/mouseleave` не всплывают.</li>
</ol> </ol>
Эти события более интуитивно понятны. Курсор заходит на элемент -- срабатывает `mouseenter`, а затем -- неважно, куда он внутри него переходит, `mouseleave` будет, когда курсор окажется за пределами элемента. Эти события более интуитивно понятны.
Курсор заходит на элемент -- срабатывает `mouseenter`, а затем -- неважно, куда он внутри него переходит, `mouseleave` будет, когда курсор окажется за пределами элемента.
[online]
Вы можете увидеть, как они работают проведя курсором над голубым `DIV'ом` ниже. Обработчик стоит только на внешнем, синем элементе. Обратите внимание -- лишних событий при переходе на красного потомка нет! Вы можете увидеть, как они работают проведя курсором над голубым `DIV'ом` ниже. Обработчик стоит только на внешнем, синем элементе. Обратите внимание -- лишних событий при переходе на красного потомка нет!
[codetabs height=340 src="mouseleave"] [codetabs height=340 src="mouseleave"]
[/online]
## Делегирование -- проблема mouseenter/leave ## Делегирование
События `mouseenter/leave` более наглядны и понятны, но они не всплывают, а значит с ними нельзя использовать делегирование. События `mouseenter/leave` более наглядны и понятны, но они не всплывают, а значит с ними нельзя использовать делегирование.
Представьте себе, что нам нужно обработать вход/выход мыши для ячеек таблицы. А в таблице таких ячеек тысяча. Представим, что нам нужно обработать вход/выход мыши для ячеек таблицы. А в таблице таких ячеек тысяча.
Естественное решение -- поставить обработчик на верхний элемент `<table>` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, а срабатывают именно на том элементе, на котором стоит обработчик и только на нём. Естественное решение -- поставить обработчик на верхний элемент `<table>` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, они срабатывают именно на том элементе, на котором стоит обработчик и только на нём.
Это легко видеть в примере ниже: обработчики `mouseenter/leave` стоят на `<table>` и сработают при входе-выходе из таблицы, получить из них какую-то информацию о переходах по её ячейкам не представляется возможным: Если обработчики `mouseenter/leave` стоят на `<table>`, то они сработают при входе-выходе из таблицы, но получить из них какую-то информацию о переходах по её ячейкам невозможно.
[codetabs height=480 src="mouseleave-table"]
Не беда -- воспользуемся `mouseover/mouseout`. Не беда -- воспользуемся `mouseover/mouseout`.
Но мы хотели бы, чтобы наши действия выполнялись только при входе-выходе в ячейку, без учета переходов внутри самих ячеек. Простейший вариант обработчиков выглядит так:
Если нужно делегирование -- нужно использовать `mouseover/out`.
Получится так:
[codetabs height=450 src="mouseenter-mouseleave-delegation"]
В этом примере код обработчиков выглядит так:
```js ```js
table.onmouseover = function(event) { table.onmouseover = function(event) {
@ -129,20 +172,33 @@ table.onmouseout = function(event) {
target.style.background = ''; target.style.background = '';
}; };
``` ```
[online]
Пока что они срабатывают на всём подряд. Их нужно фильтровать: [codetabs height=450 src="mouseenter-mouseleave-delegation"]
[/online]
В таком виде они срабатывают при переходе с любого элемента на любой. Нас же интересуют переходы строго с одной ячейки `<td>` на другую.
Нужно фильтровать события.
Один из вариантов:
<ul> <ul>
<li>Вход-выход срабатывает на любых элементах, а нам нужны именно ячейки таблицы. <li>Запоминать текущий подсвеченный `<td>` в переменной.</li>
<li>При `mouseover` проверять, остались ли мы внутри того же `<td>`, если да -- игнорировать.</li>
Для этого по `mouseover` нужно проверять, находится ли `event.target` был внутри `<td>`, это стандартная проверка при делегировании.</li> <li>При `mouseout` проверять, если мы ушли с текущего `<td>`, а не перешли куда-то внутрь него, то игнорировать.</li>
<li>Лишними для нас будут `mouseover/mouseout`, связанные с переходом на дочерние элементы внутри `<td>`. Проверить их можно по `event.target/event.relatedTarget`.</li>
</ul> </ul>
[offline]
Детали кода вы можете посмотреть в [edit src="mouseenter-mouseleave-delegation-2"]полном примере[/edit].
[/offline]
[online]
Детали кода вы можете посмотреть в примере ниже, который демонстрирует этот подход: Детали кода вы можете посмотреть в примере ниже, который демонстрирует этот подход:
[codetabs height=450 src="mouseenter-mouseleave-delegation-2"] [codetabs height=450 src="mouseenter-mouseleave-delegation-2"]
Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход. Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход.[/online]
## Особенности IE8- ## Особенности IE8-
@ -162,11 +218,13 @@ function fixRelatedTarget(e) {
## Итого ## Итого
У `mouseover, mousemove, mouseout` есть следующие особенности: У `mouseover, mousemove, mouseout` есть следующие особенности:
<ol> <ul>
<li>При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы.</li>
<li>События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE).</li> <li>События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE).</li>
<li>Событие `mouseout` срабатывает, когда мышь уходит с родительского элемента на дочерний. Используйте `mouseenter/mouseleave` или фильтруйте их, чтобы избежать излишнего реагирования.</li> <li>События `mouseover/mouseout` подразумевают, что курсор находится над одним, самым глубоким элементом. Они срабатывают при переходе с родительского элемента на дочерний.</li>
<li>При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы. Мышь может моментально возникнуть над потомком, миновав при этом его родителя.</li> </ul>
</ol>
События `mouseenter/mouseleave` не всплывают и не учитывают переходы внутри элемента.

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="440px" height="183px" viewBox="0 0 440 183" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
<title>mouseover-mouseout-from-outside.svg</title>
<desc>Created with bin/sketchtool.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="mouseover-mouseout-from-outside.svg" sketch:type="MSArtboardGroup">
<rect id="Rectangle-10" stroke="#E8C48E" stroke-width="4" stroke-dasharray="5,1,5,1" opacity="0.6" fill="#FFF9EB" sketch:type="MSShapeGroup" x="202" y="57" width="126" height="93"></rect>
<g id="noun_69008_cc" sketch:type="MSLayerGroup" transform="translate(171.000000, 8.000000)" fill="#E8C48E">
<path d="M234.020833,169 L4.97916667,169 C2.23066667,169 0,167.09875 0,164.775 L0,4.225 C0,1.90125 2.23066667,0 4.97916667,0 L234.020833,0 C236.759375,0 239,1.90125 239,4.225 L239,164.775 C239,167.09875 236.759375,169 234.020833,169 L234.020833,169 Z M9.95833333,160.55 L229.041667,160.55 L229.041667,8.45 L9.95833333,8.45 L9.95833333,160.55 L9.95833333,160.55 Z" id="Shape" sketch:type="MSShapeGroup"></path>
<path d="M229.041667,42.25 L9.95833333,42.25 C7.20983333,42.25 4.97916667,40.34875 4.97916667,38.025 C4.97916667,35.70125 7.20983333,33.8 9.95833333,33.8 L229.041667,33.8 C231.780208,33.8 234.020833,35.70125 234.020833,38.025 C234.020833,40.34875 231.780208,42.25 229.041667,42.25 L229.041667,42.25 Z" id="Shape" sketch:type="MSShapeGroup"></path>
<path d="M29.875,21.125 C29.875,23.4593125 27.6468229,25.35 24.8958333,25.35 C22.1448437,25.35 19.9166667,23.4593125 19.9166667,21.125 C19.9166667,18.7906875 22.1448437,16.9 24.8958333,16.9 C27.6468229,16.9 29.875,18.7906875 29.875,21.125 L29.875,21.125 Z" id="Shape" sketch:type="MSShapeGroup"></path>
<path d="M49.7916667,21.125 C49.7916667,23.4593125 47.5634896,25.35 44.8125,25.35 C42.0615104,25.35 39.8333333,23.4593125 39.8333333,21.125 C39.8333333,18.7906875 42.0615104,16.9 44.8125,16.9 C47.5634896,16.9 49.7916667,18.7906875 49.7916667,21.125 L49.7916667,21.125 Z" id="Shape" sketch:type="MSShapeGroup"></path>
<path d="M69.7083333,21.125 C69.7083333,23.4593125 67.4801563,25.35 64.7291667,25.35 C61.9781771,25.35 59.75,23.4593125 59.75,21.125 C59.75,18.7906875 61.9781771,16.9 64.7291667,16.9 C67.4801563,16.9 69.7083333,18.7906875 69.7083333,21.125 L69.7083333,21.125 Z" id="Shape" sketch:type="MSShapeGroup"></path>
</g>
<rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="231" y="85" width="69" height="36"></rect>
<text id="#TO" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="251.150618" y="109">#TO</tspan>
</text>
<path d="M236,94.6353921 L223.12832,94.6353921 L217.344468,101.853926 L105.198001,101.853926 L108.348404,94.146 L82,103.645409 L108.348404,113.146 L105.235092,105.438074 L217.204218,105.438074 L223.12832,112.830378 L236,112.830378 L228.709332,103.733476 L236,94.6353921" id="Fill-21" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(159.000000, 103.646000) scale(-1, 1) translate(-159.000000, -103.646000) "></path>
<text id="target" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="240" y="80">target</tspan>
</text>
<text id="relatedTarget-=-null" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="15" y="85">relatedTarget = null</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="508px" height="92px" viewBox="0 0 508 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
<title>mouseover-mouseout-over-elems.svg</title>
<desc>Created with bin/sketchtool.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="mouseover-mouseout-over-elems.svg" sketch:type="MSArtboardGroup">
<rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="374" y="21" width="90.0136719" height="52.4511719"></rect>
<text id="#TO" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="405.150618" y="53">#TO</tspan>
</text>
<rect id="Rectangle-7" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="16" y="21" width="90.0136719" height="52.4511719"></rect>
<text id="#FROM" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="36.8122396" y="53">#FROM</tspan>
</text>
<rect id="Rectangle-8" stroke="#E8C48E" stroke-width="4" stroke-dasharray="5,1,5,1" opacity="0.6" fill="#FFF9EB" sketch:type="MSShapeGroup" x="124" y="31" width="69.0072917" height="52.4511719"></rect>
<text id="&lt;DIV&gt;" opacity="0.6" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="139.384505" y="63">&lt;DIV&gt;</tspan>
</text>
<rect id="Rectangle-9" stroke="#E8C48E" stroke-width="4" stroke-dasharray="5,1,5,1" opacity="0.6" fill="#FFF9EB" sketch:type="MSShapeGroup" x="207" y="31" width="69.0072917" height="52.4511719"></rect>
<text id="&lt;DIV&gt;-2" opacity="0.6" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="222.384505" y="63">&lt;DIV&gt;</tspan>
</text>
<rect id="Rectangle-10" stroke="#E8C48E" stroke-width="4" stroke-dasharray="5,1,5,1" opacity="0.6" fill="#FFF9EB" sketch:type="MSShapeGroup" x="291" y="31" width="69.0072917" height="52.4511719"></rect>
<text id="&lt;DIV&gt;-3" opacity="0.6" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="306.384505" y="63">&lt;DIV&gt;</tspan>
</text>
<path d="M401,38.6353921 L375.340223,38.6353921 L363.810075,45.8539263 L140.245366,45.8539263 L146.525714,38.146 L94,47.6454089 L146.525714,57.146 L140.319306,49.4380737 L363.530486,49.4380737 L375.340223,56.8303775 L401,56.8303775 L386.466006,47.7334759 L401,38.6353921" id="Fill-21" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup" transform="translate(247.500000, 47.646000) scale(-1, 1) translate(-247.500000, -47.646000) "></path>
<text id="mouseover" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="340" y="17">mouseover</tspan>
</text>
<text id="mouseout" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="75" y="18">mouseout</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="278px" height="92px" viewBox="0 0 278 92" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
<title>mouseover-mouseout.svg</title>
<desc>Created with bin/sketchtool.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="mouseover-mouseout.svg" sketch:type="MSArtboardGroup">
<rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="94" y="24" width="90.0136719" height="52.4511719"></rect>
<text id="&lt;DIV&gt;" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="119.384505" y="56">&lt;DIV&gt;</tspan>
</text>
<path d="M109,51.8276766 C109,51.8276766 88.1589755,39.0886766 74.3265137,34 C73.990849,36.1044846 73.6561461,38.2078702 73.3204815,40.3112558 C50.6491708,39.1326345 27.6239215,40.8772137 6,45.5449934 C8.76995481,49.733632 11.5399096,53.9217211 14.3098644,58.1098102 C32.5800247,54.1662369 52.0341482,52.6925483 71.1891551,53.6881948 C70.8534905,55.7926793 70.5178258,57.8960649 70.1831229,60 C81.3091081,56.2091804 109,51.8276766 109,51.8276766" id="Fill-55" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<text id="mouseover" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="17" y="21">mouseover</tspan>
</text>
<path d="M268,51.8276766 C268,51.8276766 247.158975,39.0886766 233.326514,34 C232.990849,36.1044846 232.656146,38.2078702 232.320481,40.3112558 C209.649171,39.1326345 186.623921,40.8772137 165,45.5449934 C167.769955,49.733632 170.53991,53.9217211 173.309864,58.1098102 C191.580025,54.1662369 211.034148,52.6925483 230.189155,53.6881948 C229.85349,55.7926793 229.517826,57.8960649 229.183123,60 C240.309108,56.2091804 268,51.8276766 268,51.8276766" id="Fill-56" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<text id="mouseoout" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="176" y="21">mouseoout</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="233px" height="150px" viewBox="0 0 233 150" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: bin/sketchtool 1.3 (252) - http://www.bohemiancoding.com/sketch -->
<title>mouseover-to-child.svg</title>
<desc>Created with bin/sketchtool.</desc>
<defs></defs>
<g id="combined" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="mouseover-to-child.svg" sketch:type="MSArtboardGroup">
<rect id="Rectangle-7" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="12" y="14" width="212" height="117"></rect>
<text id="#FROM" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="18.9784745" y="31">#FROM</tspan>
</text>
<rect id="Rectangle-6" stroke="#E8C48E" stroke-width="4" fill="#FFF9EB" sketch:type="MSShapeGroup" x="140" y="64" width="69" height="36"></rect>
<text id="#TO" sketch:type="MSTextLayer" font-family="Open Sans" font-size="14" font-weight="526" sketch:alignment="middle" fill="#8A704D">
<tspan x="160.150618" y="88">#TO</tspan>
</text>
<path d="M151,83.0279175 C151,83.0279175 135.824497,73.7186483 125.752316,70 C125.5079,71.5378926 125.264184,73.074982 125.019768,74.6120715 C108.511532,73.7507714 91.7455739,75.0256562 76,78.436726 C78.0169574,81.4976542 80.0339148,84.5581808 82.0508721,87.6187075 C95.3543869,84.7368655 109.520011,83.6599391 123.467831,84.3875269 C123.223415,85.9254195 122.978999,87.462509 122.735284,89 C130.836729,86.2297857 151,83.0279175 151,83.0279175" id="Fill-54" stroke="#EE6B47" stroke-width="2" stroke-linecap="square" fill="#EE6B47" sketch:type="MSShapeGroup"></path>
<text id="mouseover" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="113" y="59">mouseover</tspan>
</text>
<text id="mouseout" sketch:type="MSTextLayer" font-family="Consolas" font-size="14" font-weight="bold" fill="#8A704D">
<tspan x="39" y="70">mouseout</tspan>
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.