Приложения JavaScript управляются событиями. Щелчок, наведение, перетаскивание и опускание — все это примеры событий в веб-приложениях. Когда событие инициируется элементом, вложенным в другие элементы, DOM проходит три разных этапа для обработки событий — захват, нацеливание и всплытие.

В фазе всплытия, когда событие запускается для дочернего элемента, DOM проверяет, есть ли к родительскому элементу какие-либо прослушиватели событий, и если да, запускает их. Этот процесс повторяется до тех пор, пока не будет достигнута верхняя часть документа (тег <html>). По умолчанию все события подписываются на фазу всплытия.

Это называется «пузырьком», потому что события запускаются из самого внутреннего элемента и «пузырятся» вверх до самого верха. Всплывание — это не обязательно плохо, но его следует учитывать при создании приложения.

Пример пузыря

Здесь простое приложение, которое отображает различные панели мониторинга, выбирая параметры через меню, используется для демонстрации пузырьков. Вы можете получить исходный код с GitHub. Вот содержание body документа:

<div id="side-nav-menu" class="side-nav-menu">
    <span id="side-nav-menu__close-button">&times;</span>
    <ul>
        <a href="#">Home</a>
        <a href="#">About</a>
        <a href="#">Contact</a>
        <span id="side-nav-menu__sub-menu__dashboard">Saved Dashboards</span>
        <span id="side-nav-menu__sub-menu__dashboard-content">
            <a href="#" id="side-nav-menu__sub-menu__dashboard-content__dashboard-one">Dashboard One</a>
            <a href="#" id="side-nav-menu__sub-menu__dashboard-content__dashboard-two">Dashboard Two</a>
            <a href="#" id="side-nav-menu__sub-menu__dashboard-content__dashboard-three">Dashboard Three</a>
        </span>
    </ul>
</div>

<div id="top-nav-bar">
    <span id="top-nav-bar__menu-button">&#9776;</span>
</div>

<div id="dashboard-container">
    <div id="dashboard-container__content">

    </div>
</div>

Есть три основных элемента:

  • Боковое меню, которое по умолчанию скрыто.
  • Верхнее меню, где находится кнопка, открывающая меню.
  • Область страницы, где будет отображаться «панель инструментов».

Когда вы открываете приложение в браузере, щелкните значок гамбургера в верхнем левом углу, чтобы открыть меню. Немного CSS и JavaScript обеспечивают плавный переход при открытии и закрытии меню. Существует также возможность выбрать «панель инструментов».

Кстати, о JavaScript — вот обработчики событий для каждого из элементов (помните, весь этот код можно найти здесь):

// Menu Stuff
const menuButton = document.getElementById('top-nav-bar__menu-button');
const sideNavMenu = document.getElementById('side-nav-menu');
const closeButton = document.getElementById('side-nav-menu__close-button');
const dashboardMenuItem = document.getElementById('side-nav-menu__sub-menu__dashboard');
const dashboardMenuSubItem = document.getElementById('side-nav-menu__sub-menu__dashboard-items');

// Open the menu.
menuButton.addEventListener('click', function(event) {
    sideNavMenu.style.width = '250px';
    console.log(0);
});

// Close the menu.
closeButton.addEventListener('click', function(event) {
    sideNavMenu.style.width = '0px';
    console.log(1);
});

// Another way to close the menu - by clicking on the menu itself.
sideNavMenu.addEventListener('click', function (event) {
    sideNavMenu.style.width = '0px';
    console.log(2);
});

// Show the list of dashboards in the menu.
dashboardMenuItem.addEventListener('click', function (event) {
    if (dashboardMenuSubItem.style.display === 'flex') {
        dashboardMenuSubItem.style.display = 'none';
    } else {
        dashboardMenuSubItem.style.display = 'flex';
        dashboardMenuSubItem.style.flexDirection = 'column';
    }
    console.log(3);
});

Кнопки, которые открывают и закрывают меню, делают это, устанавливая ширину элемента меню. По умолчанию ширина меню 0:

#side-nav-menu {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 0;
    position: fixed;
    z-index: 1;
    top: 0;
    left: 0;
    background-color: #536f50;
    overflow-x: hidden;
    transition: 0.5s;
}

Вернемся к файлу JavaScript, вот рендеринг панелей. Это безумно просто и показывает базовую функциональность изменения содержимого области в зависимости от того, какая кнопка была выбрана. Устанавливаемое здесь содержимое — это заголовок элемента, который выбирается в меню «Сохраненные информационные панели».

// Dashboards
const dashboardOneButton = document.getElementById('side-nav-menu__sub-menu__dashboard-items__dashboard-one');
const dashboardTwoButton = document.getElementById('side-nav-menu__sub-menu__dashboard-items__dashboard-two');
const dashboardThreeButton = document.getElementById('side-nav-menu__sub-menu__dashboard-items__dashboard-three');
const dashboardContent = document.getElementById('dashboard-container__content');

const dashboards = [dashboardOneButton, dashboardTwoButton, dashboardThreeButton];

function showDashboard(event) {
    const html = event.target.innerHTML;

    dashboardContent.innerHTML = html;
}

for (const dashboard of dashboards) {
    dashboard.addEventListener('click', function (event) {
        showDashboard(event);
        console.log(event);
    });
}

В общем, файл JavaScript:

// Menu Stuff
const menuButton = document.getElementById('top-nav-bar__menu-button');
const sideNavMenu = document.getElementById('side-nav-menu');
const closeButton = document.getElementById('side-nav-menu__close-button');
const dashboardMenuItem = document.getElementById('side-nav-menu__sub-menu__dashboard');
const dashboardMenuSubItem = document.getElementById('side-nav-menu__sub-menu__dashboard-items');

// Dashboards
const dashboardOneButton = document.getElementById('side-nav-menu__sub-menu__dashboard-items__dashboard-one');
const dashboardTwoButton = document.getElementById('side-nav-menu__sub-menu__dashboard-items__dashboard-two');
const dashboardThreeButton = document.getElementById('side-nav-menu__sub-menu__dashboard-items__dashboard-three');
const dashboardContent = document.getElementById('dashboard-container__content');

const dashboards = [dashboardOneButton, dashboardTwoButton, dashboardThreeButton];

// Event Listeners
menuButton.addEventListener('click', function(event) {
    sideNavMenu.style.width = '250px';
    console.log(0);
});

closeButton.addEventListener('click', function(event) {
    sideNavMenu.style.width = '0px';
    console.log(1);
});

sideNavMenu.addEventListener('click', function (event) {
    sideNavMenu.style.width = '0px';
    console.log(2);
});

dashboardMenuItem.addEventListener('click', function (event) {
    if (dashboardMenuSubItem.style.display === 'flex') {
        dashboardMenuSubItem.style.display = 'none';
    } else {
        dashboardMenuSubItem.style.display = 'flex';
        dashboardMenuSubItem.style.flexDirection = 'column';
    }
    console.log(3);
});

// Dashboard rendering
function showDashboard(event) {
    const html = event.target.innerHTML;

    dashboardContent.innerHTML = html;
}

for (const dashboard of dashboards) {
    dashboard.addEventListener('click', function (event) {
        showDashboard(event);
        console.log(event);
    });
}

В настоящее время при нажатии «Сохраненные информационные панели» меню закрывается. Можно было бы ожидать, что меню останется открытым, чтобы они могли выбрать панель инструментов, которую они хотят просмотреть. Это бурлит в действии.

  1. Выбирается дочерний элемент (side-nav-menu__sub-menu__dashboard), и запускается присоединенный прослушиватель событий.
  2. DOM находится в фазе «пузырькового» поиска и ищет слушателей событий одного и того же события для любых родительских элементов.
  3. Затем запускается прослушиватель событий родительского элемента (side-nav-menu).
<!-- 2 -->
<div id="side-nav-menu" class="side-nav-menu">
    <span id="side-nav-menu__close-button">&times;</span>
    <ul>
        <a href="#">Home</a>
        <a href="#">About</a>
        <a href="#">Contact</a>
        <!-- 1 -->
        <span id="side-nav-menu__sub-menu__dashboard">Saved Dashboards</span>
        <span id="side-nav-menu__sub-menu__dashboard-items">
            <a href="#" id="side-nav-menu__sub-menu__dashboard-items__dashboard-one">Dashboard One</a>
            <a href="#" id="side-nav-menu__sub-menu__dashboard-items__dashboard-two">Dashboard Two</a>
            <a href="#" id="side-nav-menu__sub-menu__dashboard-items__dashboard-three">Dashboard Three</a>
        </span>
    </ul>
</div>

Чтобы дополнительно продемонстрировать это, в каждом прослушивателе событий регистрируется console.log(INT); сообщений. Откройте консоль в инструментах разработчика вашего браузера, затем откройте меню в веб-приложении и выберите «Сохраненные информационные панели».

menuButton.addEventListener('click', function(event) {
    sideNavMenu.style.width = '250px';
    console.log(0);
});

sideNavMenu.addEventListener('click', function (event) {
    sideNavMenu.style.width = '0px';
    console.log(2);
});

dashboardMenuItem.addEventListener('click', function (event) {
    if (dashboardMenuSubItem.style.display === 'flex') {
        dashboardMenuSubItem.style.display = 'none';
    } else {
        dashboardMenuSubItem.style.display = 'flex';
        dashboardMenuSubItem.style.flexDirection = 'column';
    }
    console.log(3);
});

Когда вы щелкаете значок меню, на консоль выводится 0. Больше ничего не печатается, так как нет родительских элементов с прослушивателем событий щелчка. Но когда вы нажимаете «Сохраненные информационные панели», 3 и 2 выводятся на консоль. 3 печатается из элемента «Сохраненные информационные панели» (dashboardMenuItem), а 2 печатается из родительского элемента sideNavMenu.

0
3
2

Если бы в DOM были какие-либо другие родительские элементы с прослушивателем события клика, они также были бы активированы. Вы можете проверить это самостоятельно, добавив прослушиватель событий в файл body.

Надеюсь, идея «пузырьков» понятна на этом этапе.

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

При необходимости есть способ полностью остановить перемещение (или распространение) событий через DOM.

Распространение

передача движения, света, звука и т. д. в определенном направлении или через среду. - "Google"

Распространение событий в JavaScript описывает, как события перемещаются по DOM и в каком направлении они перемещаются.

Ранее упоминалось о трех разных фазах, которые происходят при запуске события; захват, цель и пузырение. В фазе всплытия направление распространения события перемещается от дочернего элемента вверх к родительскому элементу. На этапе захвата все наоборот.

Фаза всплытия — единственная фаза, на которую по умолчанию подписаны все события.

Чтобы остановить распространение события, следует добавить метод event.stopPropagation(); к прослушивателю событий, для которого вы хотите остановить распространение. Вот ссылка на документацию MDN для этого метода.

В этом примере я добавил его в прослушиватель событий dashboardMenuItem:

dashboardMenuItem.addEventListener('click', function (event) {
    event.stopPropagation();

    if (dashboardMenuSubItem.style.display === 'flex') {
        dashboardMenuSubItem.style.display = 'none';
    } else {
        dashboardMenuSubItem.style.display = 'flex';
        dashboardMenuSubItem.style.flexDirection = 'column';
    }
    console.log(3);
});

Это гарантирует, что меню не закроется при выборе параметра «Сохраненные информационные панели».

Заворачивать

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

С приложением, которое имеет глубоко вложенные элементы, содержащие прослушиватели событий одного типа, вы можете столкнуться с непредвиденным поведением, если не остановите распространение событий на правильном уровне в дереве DOM.

Если вы хотите узнать больше о событиях в JavaScript, ознакомьтесь с документацией MDN. Если вы ищете другие посты, посвященные JavaScript, программированию, карьере в сфере технологий и смежным темам — я пишу еженедельно прямо здесь, на Medium.