addEventListener? removeEventListener? useCapture? capture?
click? mousedown? mouseup? touchstart? touchend?
touchcancel? dragleave?
stopPropogation? bubbling? capturing?
mouseenter? mouseleave? mouseover? mouseout?
Был такой случай
Выхожу из подъезда, передо мной бабушка.
Делает шаг в сторону со словами: «Молодым везде дорога!».
Я поблагодарил и вышел первым.
Думаю — «Ну наконец-то — хоть одно нормальное правило.» :D
Забавно, что, по-умолчанию — именно так и работают события в JavaScript.
По умолчанию — все действия Прогрессивные — сначала проходит Потомок, потом Родитель.
<div on:click={() => alert('Родитель проходит вторым')}>
<button on:click={() => alert('Потомок проходит первым')}>
Молодым везде дорога
</button>
<div>Да, согласна — молодым везде дорога</div>
</div>
Есть только 1 способ начать пропускать старших:
Родитель должен высказать Консервативное мнение (capture
):
<div on:click|capture={() => alert('Родитель проходит первым')}>
<div>Нам — старикам — почёт в любом случае.</div>
<button on:click={() => alert('Потомок проходит вторым')}>
Молодым везде дорога же, не?
</button>
</div>
Если Родитель высказал Консервативное мнение — никакое Мнение Потомка не интересно.
В этом вся логика.
Логика событий
- Браузер всегда проводит 2 слушания по определённому событию (
click
в нашем случае): Сначала — все Консервативные мнения (capturing, события сcapture
). Потом — все Прогрессивные (bubbling, обычные события безcapture
). - И, в каждом слушании начинает с мнения Родителя.
И, если Родитель высказал Консервативное мнение,
вот такое Консервативное мнение Потомка — не имеет смысла:
<div on:click|capture={() => alert('Родитель проходит первым')}>
<div>Нам — старикам — почёт в любом случае.</div>
<button on:click|capture={() => alert('Потомок проходит вторым')}>
Да, согласен — старикам у нас почёт
</button>
</div>
Также, не имеет смысла Потомку высказывать Консервативное мнение если Родитель другого мнения:
<div on:click={() => alert('Родитель проходит вторым')}>
<button on:click|capture={() => alert('Потомок проходит первым')}>
Старикам у нас почёт
</button>
<div>Ты поучи жену щи варить, я принял Прогрессивное решение</div>
</div>
Потому что, если Потомок выскажет Консервативное мнение — в этом также не будет смысла,
потому что у Родителя — нет Консервативного мнения.
Значит — он не хочет идти вперёд.
JavaScript это не жизнь. Здесь возможны 2-е параллельные вселенные:
<div on:click|capture={() => alert('C1')} on:click={() => alert('4')}>
<button on:click|capture={() => alert('2')} on:click={() => alert('3')}>
Кнопка
</button>
</div>
Именно поэтому, когда мы навешиваем:
addEventListener(process)
То удаляем мы его вот так:
removeEventListener(process)
А когда мы навешиваем:
addEventListener(process, true)
То и удаляем мы его вот так:
removeEventListener(process, true)
И, без разницы где и как именно указывать — true
или {capture: true}
.
К слову removeEventListener
не будет работать, если событие навешено через html-атрибут (onclick="..."
), даже если «перенавесить» через addEventListener
.
Но, у нас есть не только click
, есть и mousedown
, mouseup
и т.д.
Они тоже имеют свой порядок.
Сначала отработают вообще все mousedown
на странице, потом mouseup
, и только потом click
.
<div
on:mousedown|capture={() => console.log('D1')} on:mousedown={() => console.log('D4')}
on:mouseup|capture={() => console.log('U1')} on:mouseup={() => console.log('U4')}
on:click|capture={() => console.log('C1')} on:click={() => console.log('C4')}
>
<button
on:mousedown|capture={() => console.log('D2')} on:mousedown={() => console.log('D3')}
on:mouseup|capture={() => console.log('U2')} on:mouseup={() => console.log('U3')}
on:click|capture={() => console.log('C2')} on:click={() => console.log('C3')}
>
Кнопка
</button>
</div>
Выведет:
> D1, D2, D3, D4, U1, U2, U3, U4, C1, C2, C3, C4
Где посмотреть достоверный список приоритетов — не знаю.
e.stopPropagation
Без него можно обойтись.
В «моём кодстайле» его использовать запрещено в пользу игры с переменными is_el_doing_smth
.
Но, во имя науки:
(П — Прогрессивное; К — Консервативное)
<div on:mouseup|capture={() => console.log('К1')}
on:mouseup={() => console.log('П3')}>
<div on:mouseup|capture|stopPropagation={() => console.log('К2')}
on:mouseup|stopPropagation={() => console.log('П2')}>
<div on:mouseup|capture={() => console.log('К3')}
on:mouseup={() => console.log('П1')}>
Поехали
</div>
</div>
</div>
Выведет:
K1, K2
Т.е. stopPropagation
навешанный на событие с capture
заблокировало все будущие mouseup
— и с capture
и без него.
Таким образом, что stopPropagation
, навешанный на событие без capture
уже не имеет смысла.
Однако, это никак не повлияет, если на эти же элементы навесить событие, которое вызывается позже, к примеру click
.
Если убрать все stopPropagation
, выведет:
К1, К2, К3, П1, П2, П3
e.preventDefault
Про него вы всё сами знаете, но, есть один момент:
<button
on:touchstart={(e) => e.preventDefault()}
on:click={() => alert('Алерт, который НЕ отработает на телефоне')}
>
Кнопка 1
</button>
<button
on:touchmove={(e) => e.preventDefault()}
on:click={() => alert('Алерт, который отработает везде')}
>
Кнопка 2
</button>
Подробнее:
https://developer.mozilla.org/en-US/docs/Web/API/Touch_events
Чем отличаются mousedown
, mouseup
и click
?
mousedown
— нам не важно где был курсор когда мы нажали мышку, важно что отпустили его мы именно на этом элементе.
mouseup
— нам не важно где мы отпустим мышку, главное, что нажата она была именно тут.
click
— нажали, поводили хоть по всему экрану, вернулись на элемент, отпустили — и вот тогда это клик.
Т.е. click
— мы могли бы реализовать сами.
Что, кстати, бывает очень нужно для реализации некого click-outside (будет в другой статье).
Потому что — пусть click
— это mousedown
и mouseup
внутри одного элемента, он при этом не понимаем на каком именно элементе мы нажали, а на каком отпустили.
Есть конечно всякие e.target
, e.currentTarget
, e.relatedTarget
, e.originalTarget
и т.д.
Но, половина из них не работает для click, а вторая половина не везде поддерживается (всё это без меня).
Из того, что работает одинаково для всех событий:
e.currentTarget
— это элемент на котором висит само событие, а
e.target
— это первый самый далёко-вложенных Потомок внутри этого элемента.
click
работает и на компе и на телефоне, т.е. никакого события touch
на телефоне не существует.
Но, на телефоне:
mousedown
— это — touchstart
mouseup
— это — touchend
К слову:
mousemove
— это — touchmove
Есть ещё mouseleave
, когда курсор покидает элемент.
Но на телефоне — нет курсора.
Однако, на телефоне, мы можем нажать и вести, и тогда mouseleave
мог бы пригодиться.
Но, это событие решили не реализовывать.
Вместо этого, сделали так, что когда мы ведём пальцем за пределы экрана (не браузера) — выбрасывается touchend
.
Но, если хочется выбрасывать событие, когда мы вышли за пределы элемента, то можно это сделать так:
<script>
let el;
</script>
<div style="height:100px;background:#f00;"
on:touchstart={(e) => el = e.currentTarget}
on:touchmove={(e) => {
const {pageX, pageY} = e.touches[0];
const realElement = document.elementFromPoint(pageX, pageY)
if (realElement !== el) {
alert('touchleave')
}
}}>
</div>
Есть ещё touchcancel
, призванный обработать конфликты нажатия несколькими пальцами, но не работает в Safari, пэтому не используем.
Есть ещё dragleave
, но это не ведение пальце, а именно перетаскивание элемента (картинки).
К слову, чем отличается mouseenter
+mouseleave
от mouseover
+mouseout
:
<div data-name="Дом" style="height:100px;background:#f00;"
on:mouseenter={(e) => {console.log('вОшли Дом')}}
on:mouseleave={(e) => {console.log('вЫшли Дома')}}
on:mouseover={(e) => {console.log('перешли В Дом или Комнату')}}
on:mouseout={(e) => {console.log('перешли ИЗ Дома или Комнаты')}}
>
Каждый раз когда мы заходим в Дом — вызывается `mouseenter`.<br>
Каждый раз когда мы выходим из Дома — вызывается `mouseleave`.
<pre data-name="Комната" style="height:100px;width:50%;margin-left:50%;background:#000;">
Каждый раз когда курсор пересекает границу этой Комнаты внутри Дома:<br>
вызываются и mouseout и mouseover (именно в таком порядке).
</pre>
</div>
Иии, внииимаааниииеее...
Спасибо, за внимание.
Top comments (0)