Want to Contribute to us or want to have 15k+ Audience read your Article ? Or Just want to make a strong Backlink?

Базовый ультимативный гайд по событиям в JavaScript (на примере SvelteKit)

addEventListener? removeEventListener? useCapture? seize?
click on? mousedown? mouseup? touchstart? touchend?
touchcancel? dragleave?
stopPropogation? effervescent? capturing?
mouseenter? mouseleave? mouseover? mouseout?



Был такой случай

Выхожу из подъезда, передо мной бабушка.
Делает шаг в сторону со словами: «Молодым везде дорога!».
Я поблагодарил и вышел первым.
Думаю — «Ну наконец-то — хоть одно нормальное правило.» 😀

Забавно, что, по-умолчанию — именно так и работают события в JavaScript.



По умолчанию — все действия Прогрессивные — сначала проходит Потомок, потом Родитель.

<div on:click on={() => alert('Родитель проходит вторым')}>
    <button on:click on={() => alert('Потомок проходит первым')}>
        Молодым везде дорога
    </button>
    <div>Да, согласна — молодым везде дорога</div>
</div>
Enter fullscreen mode

Exit fullscreen mode



Есть только 1 способ начать пропускать старших:

Родитель должен высказать Консервативное мнение (seize):

<div on:click on|seize={() => alert('Родитель проходит первым')}>
    <div>Нам — старикам — почёт в любом случае.</div>
    <button on:click on={() => alert('Потомок проходит вторым')}>
        Молодым везде дорога же, не?
    </button>
</div>
Enter fullscreen mode

Exit fullscreen mode

Если Родитель высказал Консервативное мнение — никакое Мнение Потомка не интересно.
В этом вся логика.



Логика событий

  1. Браузер всегда проводит 2 слушания по определённому событию (click on в нашем случае):
    Сначала — все Консервативные мнения (capturing, события с seize).
    Потом — все Прогрессивные (effervescent, обычные события без seize).
  2. И, в каждом слушании начинает с мнения Родителя.

И, если Родитель высказал Консервативное мнение,
вот такое Консервативное мнение Потомка — не имеет смысла:

<div on:click on|seize={() => alert('Родитель проходит первым')}>
    <div>Нам — старикам — почёт в любом случае.</div>
    <button on:click on|seize={() => alert('Потомок проходит вторым')}>
        Да, согласен — старикам у нас почёт
    </button>
</div>
Enter fullscreen mode

Exit fullscreen mode

Также, не имеет смысла Потомку высказывать Консервативное мнение если Родитель другого мнения:

<div on:click on={() => alert('Родитель проходит вторым')}>
    <button on:click on|seize={() => alert('Потомок проходит первым')}>
        Старикам у нас почёт
    </button>
    <div>Ты поучи жену щи варить, я принял Прогрессивное решение</div>
</div>
Enter fullscreen mode

Exit fullscreen mode

Потому что, если Потомок выскажет Консервативное мнение — в этом также не будет смысла,
потому что у Родителя — нет Консервативного мнения.
Значит — он не хочет идти вперёд.



JavaScript это не жизнь. Здесь возможны 2-е параллельные вселенные:

<div on:click on|seize={() => alert('C1')} on:click on={() => alert('4')}>
    <button on:click on|seize={() => alert('2')} on:click on={() => alert('3')}>
        Кнопка
    </button>
</div>
Enter fullscreen mode

Exit fullscreen mode

Именно поэтому, когда мы навешиваем:
addEventListener(course of)
То удаляем мы его вот так:
removeEventListener(course of)
А когда мы навешиваем:
addEventListener(course of, true)
То и удаляем мы его вот так:
removeEventListener(course of, true)

И, без разницы где и как именно указывать — true или {seize: true}.
К слову removeEventListener не будет работать, если событие навешено через html-атрибут (onclick="..."), даже если «перенавесить» через addEventListener.



Но, у нас есть не только click on, есть и mousedown, mouseup и т.д.

Они тоже имеют свой порядок.
Сначала отработают вообще все mousedown на странице, потом mouseup, и только потом click on.

<div
    on:mousedown|seize={() => console.log('D1')} on:mousedown={() => console.log('D4')}
    on:mouseup|seize={() => console.log('U1')} on:mouseup={() => console.log('U4')}
    on:click on|seize={() => console.log('C1')} on:click on={() => console.log('C4')}
>
    <button
        on:mousedown|seize={() => console.log('D2')} on:mousedown={() => console.log('D3')}
        on:mouseup|seize={() => console.log('U2')} on:mouseup={() => console.log('U3')}
        on:click on|seize={() => console.log('C2')} on:click on={() => console.log('C3')}
    >
        Кнопка
    </button>
</div>
Enter fullscreen mode

Exit fullscreen mode

Выведет:
> D1, D2, D3, D4, U1, U2, U3, U4, C1, C2, C3, C4

Где посмотреть достоверный список приоритетов — не знаю.



e.stopPropagation

Без него можно обойтись.
В «моём кодстайле» его использовать запрещено в пользу игры с переменными is_el_doing_smth.
Но, во имя науки:

(П — Прогрессивное; К — Консервативное)

<div on:mouseup|seize={() => console.log('К1')}
     on:mouseup={() => console.log('П3')}>
    <div on:mouseup|seize|stopPropagation={() => console.log('К2')}
         on:mouseup|stopPropagation={() => console.log('П2')}>
        <div on:mouseup|seize={() => console.log('К3')}
             on:mouseup={() => console.log('П1')}>
            Поехали
        </div>
    </div>
</div>
Enter fullscreen mode

Exit fullscreen mode

Выведет:

K1, K2

Т.е. stopPropagation навешанный на событие с seize заблокировало все будущие mouseup — и с seize и без него.
Таким образом, что stopPropagation, навешанный на событие без seize уже не имеет смысла.
Однако, это никак не повлияет, если на эти же элементы навесить событие, которое вызывается позже, к примеру click on.

Если убрать все stopPropagation, выведет:

К1, К2, К3, П1, П2, П3

Про e.preventDefault вы всё сами знаете, давайте лучше о —



Чем отличаются mousedown, mouseup и click on?

mousedown — нам не важно где был курсор когда мы нажали мышку, важно что отпустили его мы именно на этом элементе.
mouseup — нам не важно где мы отпустим мышку, главное, что нажата она была именно тут.
click on — нажали, поводили хоть по всему экрану, вернулись на элемент, отпустили — и вот тогда это клик.

Т.е. click on — мы могли бы реализовать сами.
Что, кстати, бывает очень нужно для реализации некого click-outside (будет в другой статье).
Потому что — пусть click on — это mousedown и mouseup внутри одного элемента, он при этом не понимаем на каком именно элементе мы нажали, а на каком отпустили.
Есть конечно всякие e.goal, e.currentTarget, e.relatedTarget, e.originalTarget и т.д.
Но, половина из них не работает для click on, а вторая половина не везде поддерживается (всё это без меня).

Из того, что работает одинаково для всех событий:
e.currentTarget — это элемент на котором висит само событие, а
e.goal — это первый самый далёко-вложенных Потомок внутри этого элемента.

click on работает и на компе и на телефоне, т.е. никакого события contact на телефоне не существует.

Но, на телефоне:
mousedown — это — touchstart
mouseup — это — touchend
К слову:
mousemove — это — touchmove

Есть ещё mouseleave, когда курсор покидает элемент.
Но на телефоне — нет курсора.
Однако, на телефоне, мы можем нажать и вести, и тогда mouseleave мог бы пригодиться.
Но, это событие решили не реализовывать.
Вместо этого, сделали так, что когда мы ведём пальцем за пределы экрана (не браузера) — выбрасывается touchend.
Но, если хочется выбрасывать событие, когда мы вышли за пределы элемента, то можно это сделать так:

<script>
    let el;
</script>
<div model="top:100px;background:#f00;"
     on:touchstart={(e) => el = e.currentTarget}
     on:touchmove={(e) => {
         const {pageX, pageY} = e.touches[0];
         const realElement = doc.elementFromPoint(pageX, pageY)
         if (realElement !== el) {
             alert('touchleave')
         }
     }}>
</div>
Enter fullscreen mode

Exit fullscreen mode

Есть ещё touchcancel, призванный обработать конфликты нажатия несколькими пальцами, но не работает в Safari, пэтому не используем.
Есть ещё dragleave, но это не ведение пальце, а именно перетаскивание элемента (картинки).

К слову, чем отличается mouseenter+mouseleave от mouseover+mouseout:

<div data-name="Дом" model="top: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="Комната" model="top:100px;width:50%;margin-left:50%;background:#000;">
        Каждый раз когда курсор пересекает границу этой Комнаты внутри Дома:<br>
            вызываются и mouseout и mouseover (именно в таком порядке).
    </pre>
</div>
Enter fullscreen mode

Exit fullscreen mode



Иии, внииимаааниииеее…

Спасибо, за внимание.

Add a Comment

Your email address will not be published. Required fields are marked *

Want to Contribute to us or want to have 15k+ Audience read your Article ? Or Just want to make a strong Backlink?