Internet Windows Android

O introducere la evenimentele popup. JavaScript - Evenimente cu bubuitură Cum funcționează majoritatea acestora

Buna! În acest tutorial, vreau să vorbesc despre un concept atât de important precum bubblingul și interceptarea evenimentelor. Bubbling este un fenomen în care, dacă faceți clic pe un element copil, evenimentul se propagă către părintele său.

Este foarte util atunci când procesați liste sau tabele mari imbricate, pentru a nu atribui un handler de eveniment fiecărui element, puteți atribui un handler pentru fiecare element părinte, iar evenimentul se va propaga deja tuturor elementelor imbricate din părinte. Să luăm un exemplu.

Acest handler pentru

va funcționa dacă faceți clic pe eticheta atașată sau :

Faceți clic pe EM, se va declanșa handler-ul DIV

După cum puteți vedea, atunci când faceți clic pe elementul em imbricat, se declanșează gestionarul de pe div. De ce se întâmplă? Citiți mai departe pentru a afla.

Suprafață

Deci, principiul de bază al suprafeței este:

Pentru orice eveniment, nu contează dacă mouse-ul se deplasează deasupra elementului, evenimentul va fi declanșat mai întâi pe elementul părinte și apoi se va răspândi de-a lungul lanțului către toate elementele imbricate.

De exemplu, să presupunem că există 3 elemente imbricate FORM> DIV> P, fiecare cu un handler de evenimente:

FORMĂ
DIV

Bulbularea asigură un clic pe elementul interior

Vor apela mai întâi gestionarul de clicuri (dacă există unul)

Acest proces se numește clocot, deoarece evenimentele par să „plutească” din elementul interior în sus prin părinții lor, la fel ca o bulă de aer care plutește în apă, deci puteți găsi și definiția clocotirii, ei bine, este doar din cuvântul englezesc bubbling .

Accesarea elementului țintă event.target

Pentru a afla pe ce element am prins acest eveniment sau altul, există metoda event.target. (citiți despre obiectul evenimentului).

  • eveniment.tinta- acesta este de fapt elementul original pe care s-a produs evenimentul.
  • acest- acesta este întotdeauna elementul curent, care a atins balonul, iar handlerul execută în prezent pe acesta.

De exemplu, dacă aveți instalat un singur handler form.onclick, acesta va prinde toate clicurile din formular. În același timp, oriunde există un clic în interior, acesta va apărea în continuare la element

, pe care se va declanșa handlerul.

Unde:

  • acest(= event.currentTarget) va fi întotdeauna forma însăși, deoarece handler-ul a fost declanșat pe acesta.
  • eveniment.tinta va conține un link către un anumit element din formular, cel mai imbricat pe care s-a produs clicul.

În principiu, acest lucru poate fi același cu event.target dacă se face clic pe formular și nu mai există elemente în formular.

Oprirea ascensiunii

De obicei, clocotirea evenimentelor merge direct până la obiectul fereastră rădăcină.

Dar este posibil să oprești ascensiunea la un element intermediar.

Pentru a opri clocotirea, apelați metoda event.stopPropagation ().

Să luăm în considerare un exemplu, când se face clic pe buton, gestionarul body.onclick nu va funcționa:



Dacă un element are mai multe handler-uri setate pentru același eveniment, atunci chiar dacă bubblingul se oprește, toate vor fi executate.

Astfel, stopPropagation va împiedica propagarea evenimentului în continuare, dar toți gestionarii vor lucra la element, dar nu mai departe la elementul următor.

Pentru a opri procesarea elementului curent, browserele acceptă metoda event.stopImmediatePropagation (). Această metodă nu numai că va preveni barbotarea, dar va opri și procesarea evenimentelor pe elementul curent.

Imersiune

În standard, pe lângă „clocotirea” evenimentelor, există și „scufundare”.

Scufundările, spre deosebire de suprafață, sunt mai puțin solicitate, dar va fi util să știm despre asta.

Deci, există 3 etape de parcurgere a evenimentului:

  1. Evenimentul merge de sus în jos. Această etapă se numește „etapa de interceptare”.
  2. Evenimentul a atins un anumit element. Aceasta este „etapa obiectivului”.
  3. La urma urmei, evenimentul începe să apară. Aceasta este „etapa de suprafață”.

Standardul demonstrează acest lucru după cum urmează:

Astfel, când se face clic pe TD, evenimentul va călători de-a lungul lanțului de părinți, mai întâi în jos până la element („plonjează”), și apoi în sus („apare”), folosind manipulatori de-a lungul drumului.

Mai sus, am scris doar despre ascensiune, deoarece de fapt alte etape nu sunt folosite și trec neobservate pentru noi.

Handlers nu știu nimic despre etapa de interceptare, dar încep să lucreze de la ascensiune.

Și pentru a prinde un eveniment în etapa de interceptare, trebuie doar să utilizați:

  • Argumentul este adevărat, atunci evenimentul va fi interceptat la coborâre.
  • Argumentul este fals, atunci evenimentul va fi prins în clocot.

Exemple de

În exemplul de pe ,

,

Manevrele sunt la fel ca înainte, dar de data aceasta - în etapa de imersiune. Ei bine, pentru a vedea interceptarea în acțiune, faceți clic pe elementul din ea.

Manipulatorii vor declanșa în ordinea „de sus în jos”: FORM → DIV → P.

Codul JS aici este astfel:

Var elems = document.querySelectorAll ("form, div, p"); // închideți un handler pentru fiecare element în etapa de interceptare pentru (var i = 0; i< elems.length; i++) { elems[i].addEventListener("click", highlightThis, true); }


Nimeni nu te deranjează să atribui handler pentru ambele etape, astfel:

Var elems = document.querySelectorAll ("form, div, p"); pentru (var i = 0; i< elems.length; i++) { elems[i].addEventListener("click", highlightThis, true); elems[i].addEventListener("click", highlightThis, false); }

Faceți clic pe elementul interior

Pentru a vedea ordinea evenimentului:
Ar trebui să fie FORM -> DIV -> P -> P -> DIV -> FORM. Rețineți că elementul

Va participa la ambele etape.

Rezultate

  • Când apare un eveniment - elementul pe care s-a produs evenimentul este marcat ca eveniment.target.
  • Evenimentul călătorește mai întâi de la rădăcina documentului la event.target, invocând de-a lungul drumului handler-urile furnizate prin addEventListener (…., True).
  • Evenimentul se deplasează de la event.target până la începutul documentului, de-a lungul modului în care apelează gestionarele furnizate prin addEventListener (…., False).

Fiecare handler va avea acces la proprietățile evenimentului:

  • event.target este cel mai profund element în care evenimentul s-a întâmplat de fapt.
  • event.currentTarget (= this) - elementul pe care auto-handler-ul a declanșat-o în momentul respectiv (la care evenimentul a „atins”).
  • event.eventPhase - în care fază a fost declanșat gestionarul de evenimente (imersiune = 1, barbotare = 3).

Clocotirea poate fi oprită apelând metoda event.stopPropagation (), dar nu este recomandat să faceți acest lucru, deoarece este posibil să aveți nevoie de eveniment în cele mai neașteptate scopuri.

Interceptarea unui eveniment

Una dintre caracteristicile importante ale limbajului este interceptarea evenimentelor. Dacă cineva, de exemplu, face clic pe un buton, se apelează gestionarul de evenimente onClick corespunzător acelui buton. Cu ajutorul gestionării evenimentelor, vă puteți asigura că obiectul corespunzător ferestrei, documentului sau stratului dvs. interceptează și procesează evenimentul chiar înainte ca gestionarul de evenimente să fie chemat de obiectul butonului specificat în acest scop. La fel, un obiect din fereastra, documentul sau stratul dvs. poate procesa un semnal de eveniment chiar înainte de a ajunge la destinația normală.
Pentru a vedea pentru ce poate fi util acest lucru, să ne uităm la următorul exemplu:






Faceți clic pe acest link



După cum puteți vedea, nu specificăm programe de gestionare a evenimentelor în etichetă ... În schimb, scriem

window.captureEvents (Event.CLICK);

pentru a intercepta evenimentul Clic obiect fereastră. De obicei obiectul fereastră nu funcționează cu evenimentul Clic... Cu toate acestea, după interceptare, îl redirecționăm apoi către obiectul fereastră. Rețineți că în Eveniment.CLICK fragment CLIC trebuie să fie scrise cu majuscule. Dacă doriți să interceptați mai multe evenimente, atunci ar trebui să le separați unul de celălalt prin simboluri |. De exemplu:

window.captureEvents (Event.CLICK | Event.MOVE);

În plus, în funcție mâner () atribuit de noi rolului de gestionare a evenimentelor, folosim instrucțiunea întoarce-te adevărat;... Ceea ce înseamnă cu adevărat acest lucru este că browserul trebuie să proceseze linkul în sine după ce funcția se termină. mâner ()... Dacă scrii în schimb returnează fals; atunci totul se va termina acolo.

Dacă acum în etichetă Veți defini gestionarul de evenimente onClick, veți înțelege că acest program nu va fi apelat atunci când apare acest eveniment. Acest lucru nu este surprinzător, deoarece obiectul fereastră interceptează semnalul evenimentului chiar înainte de a ajunge la obiectul de legătură. Dacă definiți o funcție mâner () Cum

mâner funcție (e) (
alert ("Obiectul ferestrei a capturat acest eveniment!");
window.routeEvent (e);
întoarce-te adevărat;
}

atunci computerul va verifica dacă au fost definite alte gestionare de evenimente pentru acest obiect. Variabila e este obiectul nostru Eveniment, care este transmis funcției de gestionare a evenimentelor ca argument.

În plus, puteți trimite direct un semnal de eveniment către un obiect. Pentru a face acest lucru, puteți utiliza metoda handleEvent ()... Arată așa:



Faceți clic pe acest link

onClick = "alert (" Handler de evenimente pentru al doilea link! ");"> Al doilea link

Toate semnalele despre evenimentele Click sunt trimise spre procesare prin intermediul celui de-al doilea link - chiar dacă nu ați făcut clic pe niciun link!

Următorul script demonstrează modul în care scriptul dvs. poate răspunde la semnalele de tastare. Faceți clic pe o tastă și vedeți cum funcționează acest script.




Напишем небольшой скрипт, с помощью которого добавим обработчик события " click " для всех элементов страницы, а также для объектов document и window .

Să creăm o pagină HTML și să lipim codul HTML de mai sus în ea. Să inserăm scriptul, scris în JavaScript, înainte de eticheta de închidere a corpului. După aceea, deschideți pagina nou creată într-un browser web, apăsați tasta F12 și accesați consola. Acum să facem clic stânga în zona aparținând elementului puternic și să vedem cum va evolua evenimentul.

Cum să întrerupi barbotarea unui eveniment

Clocotirea unui eveniment (balon) poate fi întreruptă. În acest caz, acest eveniment nu va fi declanșat pentru elementele părinte (părinte). Metoda care este concepută pentru a opri evenimentul cu bulă (bubble) se numește stopPropagation ().

De exemplu, să schimbăm exemplul nostru de mai sus, astfel încât evenimentul să nu se ridice deasupra corpului:


Когда пользователь нажимает на кнопку А, событие путешествует таким образом:

Начало
| #document
| Фаза перехвата
| HTML
| BODY
| UL
| LI#li_1
| Кнопка А < - Событие возникает для целевого элемента
| Фаза всплывания
| LI#li_1
| UL
| BODY
| HTML
v #document

Заметьте, что мы можем проследить путь, по которому событие двигается до своего целевого элемента. В нашем случае для каждой нажатой кнопки мы можем быть уверены, что событие всплывет обратно, пройдя через своего родителя - ul элемент. Мы можем использовать это и реализовать всплывающие cобытия.

Всплывающие события

Всплывающие события - это те события, которые привязаны к элементу родителя, но исполняются лишь в случае, если они удовлетворяют какому-либо условию.

В качестве конкретного примера возьмем нашу панель инструментов:

Ul class="toolbar">


  • Теперь, зная, что любое нажатие на кнопке всплывет через элемент ul.toolbar , давайте прикрепим наш обработчик событий на него. К счастью, он у нас уже есть:

    Var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
    Теперь мы имеем намного более чистый код, и мы даже избавились от циклов! Заметьте однако, что мы заменили e.currentTarget на e.target . Причина кроется в том, что мы обрабатываем события на другом уровне.

    e.target - фактическая цель события, то, куда оно пробирается через DOM, и откуда потом будет всплывать.
    e.currentTarget - текущий элемент, который обрабатывает событие. В нашем случае, это ul.toolbar .

    Улучшенные всплывающие события

    В данный момент мы обрабатываем любое нажатие на каждый элемент, которое всплывает через ul.toolbar , но наше условие проверки слишком простое. Что произошло бы, если бы имели более сложный DOM, включащий в себя иконки и элементы, которые не были созданы для того, чтобы по ним кликали?


    Упс! Теперь, когда мы кликаем на li.separator или иконку, мы добавляем ему класс .active . Как минимум, это нехорошо. Нам нужен способ фильтровать события так, чтобы мы реагировали на нужный нам элемент.

    Создадим для этого небольшую функцию-помощника:

    Var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while((el = el.parentNode)); }; };
    Наш помощник делает две вещи. Во-первых, он обходит каждый элемент и его родителей и проверят, удовлетворяют ли они условию, переданному в параметре criteria . Если элемент удовлетворяет - помощник добавляет объекту события поле, называемое delegateTarget , в котором хранится элемент, удовлевторяющий нашим условиям. И затем вызывает обработчик. Соответственно, если ни один элемент не удовлетворяет условию, ни один обработчик не будет вызван.

    Мы можем использовать это так:

    Var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
    То, что доктор прописал: один обработчик событий, прикрепленный к одному элементу, который делает всю работу. Но делает ее только для нужных нам элементов. И он отлично реагирует на добавление и удаление объектов из DOM.

    Подводя итоги

    Мы вкратце рассмотрели основы реализации делегирования (обработки всплывающих) событий на чистом JavaScript. Это хорошо тем, что нам не нужно генерировать и прикреплять кучу обработчиков для каждого элемента.

    Если бы я хотел сделать из этого библиотеку или использовать код в разработке, я бы добавил пару вещей:

    Функция-помощник для проверки удовлетворения объекта критериям в более унифицированном и функциональном виде. Вроде:

    Var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // Больше критериев };
    Частичное использование помощника так же было бы не лишним:

    Var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
    Оригинал статьи: Understanding Delegated JavaScript Events
    (От переводчика: мой первый, судите строго.)

    Счастливого кодинга!

    Начиналось все с использования JavaScript и классов.

    Однако у меня возникла проблема. Я хотел использовать так называемые Всплывающие События, но также я хотел минимизировать зависимости, которые мне пришлось бы внедрять. Я не хотел подключать библиотеки jQuery для «этого маленького теста», толькло для того, чтобы использовать всплывающие события.

    Давайте посмотрим поближе, что такое всплывающие события, как они работают, и рассмотрим несколько путей для их реализации.

    Окей, так в чем проблема?

    Рассмотрим простой пример:

    Предположим, имеется список кнопок. Каждый раз, когда я нажимаю на одну из них, она должна стать «активной». После повторного нажатия кнопка должна вернуться в исходное состояние.

    Начнем с HTML:


    Я мог бы использовать стандартный JavaScript обработчик событий вроде такого:

    For(var i = 0; i < buttons.length; i++) { var button = buttons[i]; button.addEventListener("click", function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }); }
    Выглядит неплохо… Но работать он не будет. По крайней мере, не так, как мы этого ожидаем.

    Замыкания победили

    Для тех, кто немного знает функциональный JavaScript, проблема очевидна.

    Для остальных же кратко объясню - функция обработчика замыкается на переменную button . Однако это переменная одна, и перезаписывается каждую итерацию.

    В первой итерации переменная ссылается на первую кнопку. В последующей - на вторую, и так далее. Но, когда пользователь нажимает на кнопку, цикл уже закончился, и переменная button ссылается на последнюю кнопку, что всегда вызывает обработчик события для нее. Непорядок.

    Что нам нужно, так это отдельный контекст для каждой функции:

    Var buttons = document.querySelectorAll(".toolbar button"); var createToolbarButtonHandler = function(button) { return function() { if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; }; for(var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i])); }
    Намного лучше! А главное, правильно работает. Мы создали функцию createToolbarButtonHandle , которая возвращает обработчик события. Затем для каждой кнопки вешаем свой обработчик.

    Так в чем проблема?

    И выглядит хорошо, и работает. Несмотря на это, мы все еще можем сделать наш код лучше.

    Во-первых, мы создаем слишком много обработчиков. Для каждой кнопки внутри .toolbar мы создаем функцию и привязываем ее как обработчик события. Для трех кнопок использование памяти незначительное.

    Но если мы имеем что-то подобное:

    • // ... еще 997 элементов...

    то компьютер, конечно, не взорвется от переполнения. Однако наше использование памяти далеко от идеального. Мы выделяем огромное ее количество, хотя можно обойтись и без этого. Давайте перепишем наш код еще раз, так, чтобы использовать одну функцию несколько раз.

    Вместо того чтобы ссылаться на переменную button , чтобы следить, на какую кнопку мы нажали, мы можем использовать event объект (объект «события»), который первым аргументом передается в каждый обработчик события.

    Event объект содержит некоторые данные о событии. В нашем случае нас интересует поле currentTarget . Из него мы получим ссылку на элемент, который был нажат:

    Var toolbarButtonHandler = function(e) { var button = e.currentTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; for(var i = 0; i < buttons.length; i++) { button.addEventListener("click", toolbarButtonHandler); }
    Отлично! Мы не только упростили все до единственной функции, которая используется несколько раз, мы еще и сделали наш код более читаемым, удалив лишнюю функцию-генератор.

    Однако мы все еще можем лучше.

    Предположим, мы добавили несколько кнопок в лист уже после того, как наш код исполнился. Тогда нам тоже понадобилось бы добавлять обработчики событий для каждой из них. И нам пришлось бы хранить ссылку на этот обработчик и ссылки из других мест. Выглядит не слишком заманчиво.

    Возможно, существует и другой подход?

    Начнем с того, что разберемся, как же работают события и как они двигаются по нашему DOM.

    Как же большинство из них работает?

    Когда пользователь нажимает на элемент, генерируется событие, чтобы оповестить приложение об этом. Путешествие каждого события происходит в три стадии:
    1. Фаза перехвата
    2. Событие возникает для целевого элемента
    3. Фаза всплывания
    Пометка: не все события проходят стадию перехвата или всплывания, некоторые создаются сразу на элементе. Однако это скорее исключение из правил.

    Событие создается снаружи документа и затем последовательно перемещается по DOM иерархии до target (целевого) элемента. Как только оно добралось до своей цели, событие тем же путем выбирается из DOM элемента.

    Вот наш HTML шаблон:




    Когда пользователь нажимает на кнопку А, событие путешествует таким образом:

    Начало
    | #document
    | Фаза перехвата
    | HTML
    | BODY
    | UL
    | LI#li_1
    | Кнопка А < - Событие возникает для целевого элемента
    | Фаза всплывания
    | LI#li_1
    | UL
    | BODY
    | HTML
    v #document

    Заметьте, что мы можем проследить путь, по которому событие двигается до своего целевого элемента. В нашем случае для каждой нажатой кнопки мы можем быть уверены, что событие всплывет обратно, пройдя через своего родителя - ul элемент. Мы можем использовать это и реализовать всплывающие cобытия.

    Всплывающие события

    Всплывающие события - это те события, которые привязаны к элементу родителя, но исполняются лишь в случае, если они удовлетворяют какому-либо условию.

    В качестве конкретного примера возьмем нашу панель инструментов:

    Ul class="toolbar">


  • Теперь, зная, что любое нажатие на кнопке всплывет через элемент ul.toolbar , давайте прикрепим наш обработчик событий на него. К счастью, он у нас уже есть:

    Var toolbar = document.querySelector(".toolbar"); toolbar.addEventListener("click", function(e) { var button = e.target; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); });
    Теперь мы имеем намного более чистый код, и мы даже избавились от циклов! Заметьте однако, что мы заменили e.currentTarget на e.target . Причина кроется в том, что мы обрабатываем события на другом уровне.

    e.target - фактическая цель события, то, куда оно пробирается через DOM, и откуда потом будет всплывать.
    e.currentTarget - текущий элемент, который обрабатывает событие. В нашем случае, это ul.toolbar .

    Улучшенные всплывающие события

    В данный момент мы обрабатываем любое нажатие на каждый элемент, которое всплывает через ul.toolbar , но наше условие проверки слишком простое. Что произошло бы, если бы имели более сложный DOM, включащий в себя иконки и элементы, которые не были созданы для того, чтобы по ним кликали?


    Упс! Теперь, когда мы кликаем на li.separator или иконку, мы добавляем ему класс .active . Как минимум, это нехорошо. Нам нужен способ фильтровать события так, чтобы мы реагировали на нужный нам элемент.

    Создадим для этого небольшую функцию-помощника:

    Var delegate = function(criteria, listener) { return function(e) { var el = e.target; do { if (!criteria(el)) continue; e.delegateTarget = el; listener.apply(this, arguments); return; } while((el = el.parentNode)); }; };
    Наш помощник делает две вещи. Во-первых, он обходит каждый элемент и его родителей и проверят, удовлетворяют ли они условию, переданному в параметре criteria . Если элемент удовлетворяет - помощник добавляет объекту события поле, называемое delegateTarget , в котором хранится элемент, удовлевторяющий нашим условиям. И затем вызывает обработчик. Соответственно, если ни один элемент не удовлетворяет условию, ни один обработчик не будет вызван.

    Мы можем использовать это так:

    Var toolbar = document.querySelector(".toolbar"); var buttonsFilter = function(elem) { return elem.classList && elem.classList.contains("btn"); }; var buttonHandler = function(e) { var button = e.delegateTarget; if(!button.classList.contains("active")) button.classList.add("active"); else button.classList.remove("active"); }; toolbar.addEventListener("click", delegate(buttonsFilter, buttonHandler));
    То, что доктор прописал: один обработчик событий, прикрепленный к одному элементу, который делает всю работу. Но делает ее только для нужных нам элементов. И он отлично реагирует на добавление и удаление объектов из DOM.

    Подводя итоги

    Мы вкратце рассмотрели основы реализации делегирования (обработки всплывающих) событий на чистом JavaScript. Это хорошо тем, что нам не нужно генерировать и прикреплять кучу обработчиков для каждого элемента.

    Если бы я хотел сделать из этого библиотеку или использовать код в разработке, я бы добавил пару вещей:

    Функция-помощник для проверки удовлетворения объекта критериям в более унифицированном и функциональном виде. Вроде:

    Var criteria = { isElement: function(e) { return e instanceof HTMLElement; }, hasClass: function(cls) { return function(e) { return criteria.isElement(e) && e.classList.contains(cls); } } // Больше критериев };
    Частичное использование помощника так же было бы не лишним:

    Var partialDelgate = function(criteria) { return function(handler) { return delgate(criteria, handler); } };
    Оригинал статьи: Understanding Delegated JavaScript Events
    (От переводчика: мой первый, судите строго.)

    Счастливого кодинга!