update
This commit is contained in:
parent
962caebbb7
commit
87bf53d076
1825 changed files with 94929 additions and 0 deletions
|
@ -0,0 +1,172 @@
|
|||
# Мышь: движение mouseover/out, mouseenter/leave
|
||||
|
||||
В этой главе мы рассмотрим события, возникающие при движении мыши над элементами.
|
||||
[cut]
|
||||
## События mouseover/mouseout, свойство relatedTarget
|
||||
|
||||
Событие `mouseover` происходит, когда мышь появляется над элементом, а `mouseout` -- когда уходит из него.
|
||||
|
||||
При этом мы можем узнать, с какого элемента пришла (или на какой ушла) мышь, используя дополнительное свойство `relatedTarget`.
|
||||
|
||||
В случае `mouseover` оно содержит элемент, *с которого* пришла мышь, а для `mouseout` -- *на который* ушла.
|
||||
|
||||
В примере ниже вы можете наглядно посмотреть события `mouseover/out`, возникающие на всех элементах.
|
||||
|
||||
[example src="mouseoverout" height=220]
|
||||
|
||||
Можно заметить, что в некоторых случаях значение `relatedTarget` может быть `null`. Это вполне нормально и означает, что мышь пришла из-за пределов окна (или ушла за окно).
|
||||
|
||||
## Частота событий mousemove и mouseover/out
|
||||
|
||||
Событие `mousemove` срабатывает при передвижении мыши. Но это не значит, что каждый пиксель экрана порождает отдельное событие!
|
||||
|
||||
События `mousemove` и `mouseover/mouseout` срабатывают так часто, насколько это позволяет внутренняя система взаимодействия с мышью браузера.
|
||||
|
||||
Это означает, что если вы двигаете мышью очень быстро, то DOM-элементы, через которые мышь проходит на большой скорости, могут быть пропущены.
|
||||
|
||||
К примеру:
|
||||
<ul>
|
||||
<li>Курсор может быстро перейти *над* родительским элементом в дочерний, при этом *не вызвав событий на родителе*, как будто он через родителя никогда не проходил.</li>
|
||||
<li>Курсор может быстро выйти из дочернего элемента без генерации событий на родителе.</li>
|
||||
</ul>
|
||||
|
||||
Попробуйте увидеть это "вживую" на тестовом стенде ниже.
|
||||
|
||||
Его HTML представляет собой два вложенных `div'а`.
|
||||
|
||||
Молниеносно проведите мышью над вложенными элементами. При этом может не быть ни одного события или их получит только красный `div`, а может быть только зеленый.
|
||||
|
||||
А еще попробуйте зайти курсором мыши на красный `div` и потом быстро вывести мышь из него куда-нибудь сквозь зеленый. Если движение мыши достаточно быстрое, то родительский элемент будет проигнорирован.
|
||||
|
||||
[example height=360 src="mouseoverout-fast"]
|
||||
|
||||
|
||||
Важно иметь в виду эту особенность событий, чтобы не написать код, который рассчитан на последовательный проход над элементами.
|
||||
## "Лишний" mouseout при уходе на потомка
|
||||
|
||||
Представьте ситуацию -- курсор зашёл на элемент. Сработал `mouseover` на нём. Потом курсор идёт на дочерний... И, оказывается, на элементе-родителе при этом происходит `mouseout`! Как будто курсор с него ушёл, хотя он всего лишь перешёл на потомка.
|
||||
|
||||
**При переходе на потомка срабатывает `mouseout` на родителе.**
|
||||
|
||||
Это кажется странным, но легко объяснимо.
|
||||
|
||||
**Согласно браузерной логике, курсор мыши может быть только над *одним* элементом -- самым глубоким в DOM (и верхним по z-index).**
|
||||
|
||||
Так что если он перешел куда-нибудь, то автоматически ушёл с предыдущего элемента. Всё просто.
|
||||
|
||||
К чему это приводит на практике, можно увидеть в примере ниже. В нём красный `div` вложен в синий. На синем стоит обработчик, который записывает его `mouseover/mouseout`.
|
||||
|
||||
Зайдите на синий элемент, а потом переведите мышь на красный -- и наблюдайте за событиями:
|
||||
|
||||
[example height=360 src="mouseoverout-child"]
|
||||
|
||||
<ol>
|
||||
<li>При заходе на синий -- на нём сработает `mouseover [target: blue]`.</li>
|
||||
<li>При переходе с синего на красный -- будет `mouseout [target: blue]` -- уход с родителя.</li>
|
||||
<li>...И тут же `mouseover [target: red]` -- как ни странно, "обратный переход" на родителя.</li>
|
||||
</ol>
|
||||
|
||||
На самом деле, обратного перехода нет. Событие `mouseover` сработало на потомке (видно по `target: red`), а затем всплыло.
|
||||
|
||||
**То есть, у кода создаётся впечатление, что курсор ушёл `mouseout` с родителя, а затем тут же перешёл `mouseover` на него (за счёт всплытия `mouseover` с потомка).**
|
||||
|
||||
Как это влияет на его поведение?
|
||||
|
||||
Если действия при наведении и уходе курсора с родителя простые, например скрытие/показ подсказки, то можно вообще ничего не заметить. Ведь события происходят одновременно, подсказка будет скрыта по `mouseout` и тут же показана по `mouseover`.
|
||||
|
||||
Если же происходит что-то более сложное, то бывает важно отследить момент "настоящего" ухода, то есть понять, когда элемент зашёл на родителя, а когда ушёл -- без учёта переходов по дочерним элементам.
|
||||
|
||||
Для этого можно использовать события `mouseenter/mouseleave`, которые мы рассмотрим далее.
|
||||
|
||||
## События mouseenter и mouseleave
|
||||
|
||||
События `mouseenter/mouseleave` похожи на `mouseover/mouseout`. Они тоже срабатывают, когда курсор заходит на элемент и уходит с него, но с двумя отличиями.
|
||||
|
||||
<ol>
|
||||
<li>При переходе на потомка курсор не уходит с родителя.</li>
|
||||
<li>События `mouseenter/mouseleave` не всплывают.</li>
|
||||
</ol>
|
||||
|
||||
Эти события более интуитивно понятны. Курсор заходит на элемент -- срабатывает `mouseenter`, а затем -- неважно, куда он внутри него переходит, `mouseleave` будет, когда курсор окажется за пределами элемента.
|
||||
|
||||
Вы можете увидеть, как они работают проведя курсором над голубым `DIV'ом` ниже. Обработчик стоит только на внешнем, синем элементе. Обратите внимание -- лишних событий при переходе на красного потомка нет!
|
||||
|
||||
[example height=340 src="mouseleave"]
|
||||
|
||||
## Делегирование -- проблема mouseenter/leave
|
||||
|
||||
События `mouseenter/leave` более наглядны и понятны, но они не всплывают, а значит с ними нельзя использовать делегирование.
|
||||
|
||||
Представьте себе, что нам нужно обработать вход/выход мыши для ячеек таблицы. А в таблице таких ячеек тысяча.
|
||||
|
||||
Естественное решение -- поставить обработчик на верхний элемент `<table>` и ловить все события в нём. Но события `mouseenter/leave` не всплывают, а срабатывают именно на том элементе, на котором стоит обработчик и только на нём.
|
||||
|
||||
Это легко видеть в примере ниже: обработчики `mouseenter/leave` стоят на `<table>` и сработают при входе-выходе из таблицы, получить из них какую-то информацию о переходах по её ячейкам не представляется возможным:
|
||||
|
||||
[example height=480 src="mouseleave-table"]
|
||||
|
||||
Не беда -- воспользуемся `mouseover/mouseout`.
|
||||
|
||||
Но мы хотели бы, чтобы наши действия выполнялись только при входе-выходе в ячейку, без учета переходов внутри самих ячеек.
|
||||
|
||||
Если нужно делегирование -- нужно использовать `mouseover/out`.
|
||||
|
||||
Получится так:
|
||||
|
||||
[example height=450 src="mouseenter-mouseleave-delegation"]
|
||||
|
||||
В этом примере код обработчиков выглядит так:
|
||||
|
||||
```js
|
||||
table.onmouseover = function(event) {
|
||||
var target = event.target;
|
||||
target.style.background = 'pink';
|
||||
};
|
||||
|
||||
table.onmouseout = function(event) {
|
||||
var target = event.target;
|
||||
target.style.background = '';
|
||||
};
|
||||
```
|
||||
|
||||
Пока что они срабатывают на всём подряд. Их нужно фильтровать:
|
||||
<ul>
|
||||
<li>Вход-выход срабатывает на любых элементах, а нам нужны именно ячейки таблицы.
|
||||
|
||||
Для этого по `mouseover` нужно проверять, находится ли `event.target` был внутри `<td>`, это стандартная проверка при делегировании.</li>
|
||||
<li>Лишними для нас будут `mouseover/mouseout`, связанные с переходом на дочерние элементы внутри `<td>`. Проверить их можно по `event.target/event.relatedTarget`.</li>
|
||||
</ul>
|
||||
|
||||
Детали кода вы можете посмотреть в примере ниже, который демонстрирует этот подход:
|
||||
|
||||
[example height=450 src="mouseenter-mouseleave-delegation-2"]
|
||||
|
||||
Попробуйте по-разному, быстро или медленно заходить и выходить в ячейки таблицы. Обработчики `mouseover/mouseout` стоят на `table`, но при помощи делегирования корректно обрабатывают вход-выход.
|
||||
|
||||
## Особенности IE8-
|
||||
|
||||
В IE8- нет свойства `relatedTarget`. Вместо него используется `fromElement` для `mouseover` и `toElement` для `mouseout`.
|
||||
|
||||
Можно "исправить" несовместимость с `relatedTarget` так:
|
||||
|
||||
```js
|
||||
function fixRelatedTarget(e) {
|
||||
if (e.relatedTarget === undefined) {
|
||||
if (e.type == 'mouseover') e.relatedTarget = e.fromElement;
|
||||
if (e.type == 'mouseout') e.relatedTarget = e.toElement;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
У `mouseover, mousemove, mouseout` есть следующие особенности:
|
||||
<ol>
|
||||
<li>События `mouseover` и `mouseout` -- единственные, у которых есть вторая цель: `relatedTarget` (`toElement/fromElement` в IE).</li>
|
||||
<li>Событие `mouseout` срабатывает, когда мышь уходит с родительского элемента на дочерний. Используйте `mouseenter/mouseleave` или фильтруйте их, чтобы избежать излишнего реагирования.</li>
|
||||
<li>При быстром движении мыши события `mouseover, mousemove, mouseout` могут пропускать промежуточные элементы. Мышь может моментально возникнуть над потомком, миновав при этом его родителя.</li>
|
||||
</ol>
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue