renovations
This commit is contained in:
parent
a5e8c1219f
commit
41f07253d3
21 changed files with 536 additions and 126 deletions
|
@ -1,36 +1,65 @@
|
|||
# Мышь: движение mouseover/out, mouseenter/leave
|
||||
|
||||
В этой главе мы рассмотрим события, возникающие при движении мыши над элементами.
|
||||
|
||||
[cut]
|
||||
|
||||
## События mouseover/mouseout, свойство relatedTarget
|
||||
|
||||
Событие `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`, возникающие на всех элементах.
|
||||
|
||||
[codetabs src="mouseoverout" height=220]
|
||||
[/online]
|
||||
|
||||
Можно заметить, что в некоторых случаях значение `relatedTarget` может быть `null`. Это вполне нормально и означает, что мышь пришла из-за пределов окна (или ушла за окно).
|
||||
[warn header="`relatedTarget` может быть `null`"]
|
||||
Свойство `relatedTarget` может быть равно `null`.
|
||||
|
||||
## Частота событий mousemove и mouseover/out
|
||||
Это вполне нормально и означает, что мышь пришла не с другого элемента, а из-за пределов окна (или ушла за окно). Мы обязательно должны иметь в виду такую возможность, когда пишем код, который обращается к свойствам `event.relatedTarget`.
|
||||
[/warn]
|
||||
|
||||
## Частота событий
|
||||
|
||||
Событие `mousemove` срабатывает при передвижении мыши. Но это не значит, что каждый пиксель экрана порождает отдельное событие!
|
||||
|
||||
События `mousemove` и `mouseover/mouseout` срабатывают так часто, насколько это позволяет внутренняя система взаимодействия с мышью браузера.
|
||||
|
||||
Это означает, что если вы двигаете мышью очень быстро, то DOM-элементы, через которые мышь проходит на большой скорости, могут быть пропущены.
|
||||
Это означает, что если посетитель двигает мышью быстро, то DOM-элементы, через которые мышь проходит на большой скорости, могут быть пропущены.
|
||||
|
||||
К примеру:
|
||||
<ul>
|
||||
<li>Курсор может быстро перейти *над* родительским элементом в дочерний, при этом *не вызвав событий на родителе*, как будто он через родителя никогда не проходил.</li>
|
||||
<li>Курсор может быстро выйти из дочернего элемента без генерации событий на родителе.</li>
|
||||
</ul>
|
||||
<img src="mouseover-mouseout-over-elems.svg">
|
||||
|
||||
При быстром движении с элемента `#FROM` до элемента `#TO`, как изображено на картинке выше -- промежуточные `<DIV>` будут пропущены. Сработает только событие `mouseout` на `#FROM` и `mouseover` на `#TO`.
|
||||
|
||||
На практике это полезно, потому что таких промежуточных элементов может быть много, и если обрабатывать заход и уход с каждого -- дополнительные вычислительные затраты.
|
||||
|
||||
С другой стороны, мы должны это понимать и не рассчитывать на то, что мышь аккуратно пройдёт с одного элемента на другой и так далее. Нет, она "прыгает".
|
||||
|
||||
В частности, возможна ситуация, когда курсор прыгает в середину страницы, и при этом `relatedTarget=null`, то есть он пришёл "ниоткуда" (на самом деле извне окна):
|
||||
|
||||
<img src="mouseover-mouseout-from-outside.svg">
|
||||
|
||||
Обратим внимание ещё на такую деталь. При быстром движении курсор окажется над `#TO` сразу, даже если этот элемент глубоко в DOM. Его родители при движении сквозь них события не поймают.
|
||||
|
||||
[online]
|
||||
Попробуйте увидеть это "вживую" на тестовом стенде ниже.
|
||||
|
||||
Его HTML представляет собой два вложенных `div'а`.
|
||||
|
@ -40,22 +69,44 @@
|
|||
А еще попробуйте зайти курсором мыши на красный `div` и потом быстро вывести мышь из него куда-нибудь сквозь зеленый. Если движение мыши достаточно быстрое, то родительский элемент будет проигнорирован.
|
||||
|
||||
[codetabs height=360 src="mouseoverout-fast"]
|
||||
[/online]
|
||||
|
||||
Важно иметь в виду эту особенность событий, чтобы не написать код, который рассчитан на последовательный проход над элементами. В остальном это вполне удобно.
|
||||
|
||||
Важно иметь в виду эту особенность событий, чтобы не написать код, который рассчитан на последовательный проход над элементами.
|
||||
## "Лишний" mouseout при уходе на потомка
|
||||
|
||||
Представьте ситуацию -- курсор зашёл на элемент. Сработал `mouseover` на нём. Потом курсор идёт на дочерний... И, оказывается, на элементе-родителе при этом происходит `mouseout`! Как будто курсор с него ушёл, хотя он всего лишь перешёл на потомка.
|
||||
|
||||
**При переходе на потомка срабатывает `mouseout` на родителе.**
|
||||
|
||||
<img src="mouseover-to-child.svg">
|
||||
|
||||
Это кажется странным, но легко объяснимо.
|
||||
|
||||
**Согласно браузерной логике, курсор мыши может быть только над *одним* элементом -- самым глубоким в 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>
|
||||
|
||||
На самом деле, обратного перехода нет. Событие `mouseover` сработало на потомке (видно по `target: red`), а затем всплыло.
|
||||
[/online]
|
||||
|
||||
**То есть, у кода создаётся впечатление, что курсор ушёл `mouseout` с родителя, а затем тут же перешёл `mouseover` на него (за счёт всплытия `mouseover` с потомка).**
|
||||
|
||||
Как это влияет на его поведение?
|
||||
|
||||
Если действия при наведении и уходе курсора с родителя простые, например скрытие/показ подсказки, то можно вообще ничего не заметить. Ведь события происходят одновременно, подсказка будет скрыта по `mouseout` и тут же показана по `mouseover`.
|
||||
Если действия при наведении и уходе курсора с родителя простые, например скрытие/показ подсказки, то можно вообще ничего не заметить. Ведь события происходят сразу одно за другим, подсказка будет скрыта по `mouseout` и тут же показана по `mouseover`.
|
||||
|
||||
Если же происходит что-то более сложное, то бывает важно отследить момент "настоящего" ухода, то есть понять, когда элемент зашёл на родителя, а когда ушёл -- без учёта переходов по дочерним элементам.
|
||||
|
||||
|
@ -84,39 +132,34 @@
|
|||
События `mouseenter/mouseleave` похожи на `mouseover/mouseout`. Они тоже срабатывают, когда курсор заходит на элемент и уходит с него, но с двумя отличиями.
|
||||
|
||||
<ol>
|
||||
<li>При переходе на потомка курсор не уходит с родителя.</li>
|
||||
<li>Не учитываются переходы внутри элемента.</li>
|
||||
<li>События `mouseenter/mouseleave` не всплывают.</li>
|
||||
</ol>
|
||||
|
||||
Эти события более интуитивно понятны. Курсор заходит на элемент -- срабатывает `mouseenter`, а затем -- неважно, куда он внутри него переходит, `mouseleave` будет, когда курсор окажется за пределами элемента.
|
||||
Эти события более интуитивно понятны.
|
||||
|
||||
Курсор заходит на элемент -- срабатывает `mouseenter`, а затем -- неважно, куда он внутри него переходит, `mouseleave` будет, когда курсор окажется за пределами элемента.
|
||||
|
||||
[online]
|
||||
|
||||
Вы можете увидеть, как они работают проведя курсором над голубым `DIV'ом` ниже. Обработчик стоит только на внешнем, синем элементе. Обратите внимание -- лишних событий при переходе на красного потомка нет!
|
||||
|
||||
[codetabs height=340 src="mouseleave"]
|
||||
[/online]
|
||||
|
||||
## Делегирование -- проблема mouseenter/leave
|
||||
## Делегирование
|
||||
|
||||
События `mouseenter/leave` более наглядны и понятны, но они не всплывают, а значит с ними нельзя использовать делегирование.
|
||||
|
||||
Представьте себе, что нам нужно обработать вход/выход мыши для ячеек таблицы. А в таблице таких ячеек тысяча.
|
||||
Представим, что нам нужно обработать вход/выход мыши для ячеек таблицы. А в таблице таких ячеек тысяча.
|
||||
|
||||
Естественное решение -- поставить обработчик на верхний элемент `<table>` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, а срабатывают именно на том элементе, на котором стоит обработчик и только на нём.
|
||||
Естественное решение -- поставить обработчик на верхний элемент `<table>` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, они срабатывают именно на том элементе, на котором стоит обработчик и только на нём.
|
||||
|
||||
Это легко видеть в примере ниже: обработчики `mouseenter/leave` стоят на `<table>` и сработают при входе-выходе из таблицы, получить из них какую-то информацию о переходах по её ячейкам не представляется возможным:
|
||||
|
||||
[codetabs height=480 src="mouseleave-table"]
|
||||
Если обработчики `mouseenter/leave` стоят на `<table>`, то они сработают при входе-выходе из таблицы, но получить из них какую-то информацию о переходах по её ячейкам невозможно.
|
||||
|
||||
Не беда -- воспользуемся `mouseover/mouseout`.
|
||||
|
||||
Но мы хотели бы, чтобы наши действия выполнялись только при входе-выходе в ячейку, без учета переходов внутри самих ячеек.
|
||||
|
||||
Если нужно делегирование -- нужно использовать `mouseover/out`.
|
||||
|
||||
Получится так:
|
||||
|
||||
[codetabs height=450 src="mouseenter-mouseleave-delegation"]
|
||||
|
||||
В этом примере код обработчиков выглядит так:
|
||||
Простейший вариант обработчиков выглядит так:
|
||||
|
||||
```js
|
||||
table.onmouseover = function(event) {
|
||||
|
@ -129,20 +172,33 @@ table.onmouseout = function(event) {
|
|||
target.style.background = '';
|
||||
};
|
||||
```
|
||||
[online]
|
||||
|
||||
Пока что они срабатывают на всём подряд. Их нужно фильтровать:
|
||||
[codetabs height=450 src="mouseenter-mouseleave-delegation"]
|
||||
|
||||
[/online]
|
||||
|
||||
В таком виде они срабатывают при переходе с любого элемента на любой. Нас же интересуют переходы строго с одной ячейки `<td>` на другую.
|
||||
|
||||
Нужно фильтровать события.
|
||||
|
||||
Один из вариантов:
|
||||
<ul>
|
||||
<li>Вход-выход срабатывает на любых элементах, а нам нужны именно ячейки таблицы.
|
||||
|
||||
Для этого по `mouseover` нужно проверять, находится ли `event.target` был внутри `<td>`, это стандартная проверка при делегировании.</li>
|
||||
<li>Лишними для нас будут `mouseover/mouseout`, связанные с переходом на дочерние элементы внутри `<td>`. Проверить их можно по `event.target/event.relatedTarget`.</li>
|
||||
<li>Запоминать текущий подсвеченный `<td>` в переменной.</li>
|
||||
<li>При `mouseover` проверять, остались ли мы внутри того же `<td>`, если да -- игнорировать.</li>
|
||||
<li>При `mouseout` проверять, если мы ушли с текущего `<td>`, а не перешли куда-то внутрь него, то игнорировать.</li>
|
||||
</ul>
|
||||
|
||||
[offline]
|
||||
Детали кода вы можете посмотреть в [edit src="mouseenter-mouseleave-delegation-2"]полном примере[/edit].
|
||||
[/offline]
|
||||
|
||||
[online]
|
||||
Детали кода вы можете посмотреть в примере ниже, который демонстрирует этот подход:
|
||||
|
||||
[codetabs height=450 src="mouseenter-mouseleave-delegation-2"]
|
||||
|
||||
Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход.
|
||||
Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход.[/online]
|
||||
|
||||
## Особенности IE8-
|
||||
|
||||
|
@ -162,11 +218,13 @@ function fixRelatedTarget(e) {
|
|||
## Итого
|
||||
|
||||
У `mouseover, mousemove, mouseout` есть следующие особенности:
|
||||
<ol>
|
||||
<ul>
|
||||
<li>При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы.</li>
|
||||
<li>События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE).</li>
|
||||
<li>Событие `mouseout` срабатывает, когда мышь уходит с родительского элемента на дочерний. Используйте `mouseenter/mouseleave` или фильтруйте их, чтобы избежать излишнего реагирования.</li>
|
||||
<li>При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы. Мышь может моментально возникнуть над потомком, миновав при этом его родителя.</li>
|
||||
</ol>
|
||||
<li>События `mouseover/mouseout` подразумевают, что курсор находится над одним, самым глубоким элементом. Они срабатывают при переходе с родительского элемента на дочерний.</li>
|
||||
</ul>
|
||||
|
||||
События `mouseenter/mouseleave` не всплывают и не учитывают переходы внутри элемента.
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue