«Хороший сайт делается для людей, а поисковики приходят следом»

— из принципов редакции SeoLiga.ru
Главная » Статьи » Веб-программирование » JavaScript на странице: события и DOM

JavaScript на странице: события и DOM

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

В статье разберём обе темы на практике: как работать с деревом элементов, как подписываться на события, чем отличается всплытие от погружения и зачем нужно делегирование. Примеры написаны на чистом JavaScript, без библиотек.

Что такое DOM

Когда браузер загружает страницу, он разбирает HTML и строит из него дерево объектов. Каждый тег, каждый фрагмент текста становится узлом. Это дерево и называется DOM — объектная модель документа. JavaScript работает именно с ней, а не с исходным текстом разметки.

Важные следствия:

  • изменение узла в скрипте сразу меняет отображение страницы;
  • DOM существует только пока открыта страница, исходный файл при этом не меняется;
  • у каждого элемента есть родитель, дети и соседи, и по этим связям можно перемещаться;
  • элементы имеют свойства и методы: содержимое, атрибуты, стили, список классов.

Поиск элементов

Прежде чем что-то менять, элемент нужно найти. Современный и универсальный способ — запрос по селектору, такому же, как в стилях.

const title = document.querySelector('h1');
const items = document.querySelectorAll('.menu li');
const form = document.getElementById('order-form');

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

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

Чтение и изменение содержимого

У элементов есть несколько способов работы с содержимым, и между ними важная разница.

  • textContent читает и записывает только текст. Теги в нём не интерпретируются, поэтому способ безопасен.
  • innerHTML работает с разметкой. Удобно, но опасно: если вставить в него данные пользователя, злоумышленник может внедрить свой скрипт.
  • value используется для полей ввода.
  • setAttribute и getAttribute управляют атрибутами.
const msg = document.querySelector('#message');
msg.textContent = 'Заявка отправлена';
msg.classList.add('success');
msg.classList.remove('error');

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

Создание и удаление элементов

Новые элементы создаются методом создания, затем настраиваются и добавляются в нужное место дерева.

const li = document.createElement('li');
li.textContent = 'Новый пункт';
document.querySelector('#list').appendChild(li);

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

События: основы

Событие — это сигнал о том, что что-то произошло: пользователь кликнул, нажал клавишу, изменил поле, закончилась загрузка страницы. Чтобы реагировать, на элемент вешают обработчик — функцию, которую браузер вызовет при наступлении события.

const button = document.querySelector('#send');
button.addEventListener('click', function (event) {
  console.log('Нажата кнопка');
});

Рекомендуемый способ подписки — метод добавления слушателя. Он позволяет навесить несколько обработчиков на одно событие, не затирая друг друга, и аккуратно снимать их при необходимости. Старый способ через свойство вроде onclick хранит единственный обработчик, и повторное присвоение его заменяет.

Самые востребованные события:

  • click, dblclick: клики мышью;
  • input, change: изменение значения в поле;
  • submit: отправка формы;
  • keydown, keyup: нажатие клавиш;
  • mouseover, mouseout: наведение курсора;
  • scroll, resize: прокрутка и изменение размера окна;
  • DOMContentLoaded, load: готовность документа и полная загрузка ресурсов.

Объект события

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

  • event.target — элемент, на котором событие возникло на самом деле.
  • event.currentTarget — элемент, на котором висит обработчик.
  • event.preventDefault() отменяет стандартное поведение: переход по ссылке, отправку формы.
  • event.stopPropagation() прекращает дальнейшее распространение события.
  • event.key содержит название нажатой клавиши.
form.addEventListener('submit', function (event) {
  event.preventDefault();
  if (!form.elements.email.value) {
    alert('Укажите адрес почты');
  }
});

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

Всплытие и погружение

Событие не ограничивается элементом, на котором оно произошло. Сначала оно проходит вниз от корня документа к цели (фаза погружения), затем поднимается обратно вверх (фаза всплытия). По умолчанию обработчики срабатывают на всплытии.

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

Остановка всплытия кажется простым решением, но злоупотреблять ей не стоит: она ломает чужие обработчики, например компоненты аналитики, которые ждут события на верхнем уровне. Применяйте её точечно и осознанно.

Делегирование событий

Представьте таблицу на сотню строк с кнопкой удаления в каждой. Навешивать сотню обработчиков неудобно и затратно. К тому же новые строки, добавленные скриптом позже, останутся без обработчиков.

Решение — делегирование: один обработчик на общем родителе, который с помощью объекта события определяет, по какому именно элементу кликнули.

document.querySelector('#table').addEventListener('click', function (event) {
  const btn = event.target.closest('.delete');
  if (!btn) return;
  const row = btn.closest('tr');
  row.remove();
});

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

Когда запускать скрипт

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

  1. Подключайте скрипт в конце тела документа.
  2. Добавляйте атрибут отложенной загрузки, чтобы скрипт выполнялся после разбора разметки.
  3. Оборачивайте код в обработчик события готовности документа.

Второй вариант сегодня предпочтителен: страница не блокируется загрузкой, а скрипт гарантированно видит готовое дерево.

Типичные ошибки начинающих

  • Обращение к элементу до того, как он создан.
  • Использование innerHTML с пользовательскими данными.
  • Подписка на события в цикле без делегирования, особенно на динамических списках.
  • Забытое снятие обработчиков при удалении элементов, из-за чего растёт потребление памяти.
  • Путаница между event.target и event.currentTarget.
  • Бесконтрольная остановка всплытия.
  • Частые обращения к DOM в цикле: чтение и запись размеров вперемешку вызывают лишние пересчёты вёрстки.
  • Обработчики прокрутки и изменения размера без ограничения частоты вызовов.

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

Влияние на SEO и доступность

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

Не забывайте о клавиатуре и вспомогательных технологиях: если вы делаете кликабельным не кнопку, а обычный блок, добавьте поддержку клавиш и понятные атрибуты. Лучше используйте настоящие кнопки и ссылки: они из коробки поддерживают фокус и клавиатуру.

Заключение

DOM и события — фундамент клиентского JavaScript. Научившись находить элементы, менять их содержимое и классы, подписываться на события и использовать делегирование, вы сможете написать большинство интерактивных элементов сайта без громоздких библиотек: вкладки, раскрывающиеся списки, проверку форм, динамические списки.

Практический совет: возьмите небольшую задачу, например список дел с добавлением и удалением пунктов, и реализуйте её целиком на чистом JavaScript. Эта простая работа затрагивает поиск элементов, создание узлов, события и делегирование, то есть всё, что мы разобрали. После неё библиотеки и фреймворки станут понятнее, потому что вы будете знать, что они делают под капотом.

АВ
Андрей Ветров
Автор SeoLiga.ru. Пишет о практике: что работает на реальных проектах, а что лучше обойти стороной. Хотите предложить свою статью? Загляните в раздел «Авторам».