</> Praktum

Главная / JavaScript / DOM и события

Урок 5 · JavaScript

DOM и события: оживляем страницу

Редакция PraktumОбновлено 15 минут на урок с практикойУровень: новичок

DOM (Document Object Model) — дерево объектов, в которое браузер превращает HTML-страницу. Каждый тег становится узлом, с которым JavaScript может работать: найти через querySelector, поменять текст и классы, создать новый элемент или удалить старый. События — клик, ввод, отправка формы — запускают этот код в ответ на действия человека.

Коротко
  • Найти: document.querySelector('.button') — первый элемент или null.
  • Текст — textContent, классы — classList, атрибуты — setAttribute.
  • Создать — createElement + append, удалить — remove().
  • Реакция на действие — addEventListener('click', handler).
  • Скрипт подключай с defer, чтобы элементы уже существовали.

Зачем этот урок сейчас

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

DOM — дерево страницы

<body>
  <h1>Список задач</h1>
  <ul class="tasks">
    <li>Выучить DOM</li>
  </ul>
</body>

Браузер строит из этого HTML дерево: document → html → body → h1 и ul → li. JavaScript работает именно с деревом, а не с текстом файла. Посмотреть его можно во вкладке Elements в DevTools: там видно текущее состояние страницы после всех изменений скриптом.

Как найти элемент

const title = document.querySelector('h1');             // первый h1
const list = document.querySelector('.tasks');           // по классу
const save = document.getElementById('save');            // по id
const items = document.querySelectorAll('.tasks li');    // все совпадения

for (const item of items) console.log(item.textContent);

querySelector принимает CSS-селектор и возвращает первый подходящий элемент или null. querySelectorAll возвращает список всех совпадений, его перебирают через for...of. Если элемента нет, следующая строка вроде title.textContent упадёт с ошибкой — проверяй результат.

Как изменить элемент

title.textContent = 'Мои задачи';            // текст
title.classList.add('is-done');             // добавить класс
title.classList.toggle('is-open');          // переключить класс
save.setAttribute('aria-pressed', 'true');  // атрибут
save.disabled = true;                       // свойство
list.hidden = false;                        // показать скрытый элемент
title.style.color = 'tomato';               // стиль (лучше классом)
Что чем менять
Нужно изменитьСпособ
ТекстtextContent
Внешний видclassList.add/remove/toggle
АтрибутsetAttribute, getAttribute, removeAttribute
Данныеdataset: data-id → el.dataset.id
Показать / скрытьhidden

Внешний вид меняй классами, а не style: стили остаются в CSS, а скрипт только переключает состояние. textContent вставляет текст как есть и безопасен для данных от пользователя.

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

const li = document.createElement('li');
li.textContent = 'Повторить события';
li.classList.add('task');
list.append(li);          // в конец списка
list.prepend(li);         // в начало

li.remove();              // удалить элемент

createElement создаёт элемент в памяти, а append вставляет его на страницу. Так данные из массива превращаются в список: цикл по задачам, для каждой — новый li. Строку HTML через innerHTML используй только для своей разметки без данных пользователя: иначе в страницу можно внедрить чужой скрипт.

События: клик и ввод

const button = document.querySelector('.theme-toggle');

button.addEventListener('click', () => {
  document.body.classList.toggle('dark');
});

const input = document.querySelector('#search');
input.addEventListener('input', () => {
  console.log(input.value);
});

addEventListener подписывает элемент на событие и вызывает функцию каждый раз, когда оно происходит. click срабатывает от мыши, касания и клавиатуры (Enter и пробел на кнопке), input — при каждом изменении поля. Все параметры и тонкости — в справочнике addEventListener.

Событие формы

const form = document.querySelector('#order');

form.addEventListener('submit', (event) => {
  event.preventDefault();                         // не перезагружать страницу
  const name = form.elements.name.value.trim();
  form.querySelector('.result').textContent = name
    ? `Спасибо, ${name}!`
    : 'Напишите имя';
});

Форма по умолчанию отправляется и перезагружает страницу. event.preventDefault() отменяет это, если результат нужно показать здесь же. Слушай submit на форме, а не клик по кнопке: тогда сработает и Enter в поле.

Один обработчик на список

list.addEventListener('click', (event) => {
  const button = event.target.closest('.delete');
  if (!button) return;
  button.closest('li').remove();
});

Если элементы списка добавляются и удаляются, вешай один обработчик на родителя. event.target — то, куда кликнули, а closest поднимается к нужной кнопке. Новые элементы работают сразу, без новых подписок.

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

<head>
  <script src="app.js" defer></script>
</head>

Скрипт ищет элементы, которые должны уже существовать. Если подключить его в head без атрибутов, он выполнится раньше, чем браузер разберёт body, и querySelector вернёт null. Атрибут defer откладывает выполнение до разбора всего HTML. Второй вариант — подключать скрипт перед закрывающим </body>.

textContent или innerHTML

textContent

Вставляет текст как есть: теги становятся текстом. Безопасно для данных пользователя.

innerHTML

Разбирает строку как HTML. Удобно для своей разметки, опасно для данных пользователя: можно внедрить скрипт.

Частые ошибки

  • Подключают скрипт в head без defer и получают null при поиске элементов.
  • Путают querySelector (один элемент) и querySelectorAll (список).
  • Передают в обработчик вызов функции вместо самой функции.
  • Меняют стили через style вместо переключения классов.
  • Вставляют данные пользователя через innerHTML.

Доступность

Вешай клики на кнопки и ссылки, а не на div: только они получают фокус и работают с клавиатуры. Если скрипт показывает и прячет блок, меняй и aria-expanded у кнопки. Важные изменения на странице (результат, ошибка) выводи в область с aria-live="polite", чтобы скринридер их объявил.

Поддержка браузерами

querySelector, classList, createElement, append, remove и addEventListener работают во всех актуальных браузерах. Атрибут defer поддерживается всеми браузерами.

Практика

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

Показать решение
const form = document.querySelector('#todo-form');
const input = document.querySelector('#todo-input');
const list = document.querySelector('#todo-list');

form.addEventListener('submit', (event) => {
  event.preventDefault();
  const text = input.value.trim();
  if (!text) return;

  const li = document.createElement('li');
  const span = document.createElement('span');
  span.textContent = text;
  const del = document.createElement('button');
  del.type = 'button';
  del.className = 'delete';
  del.textContent = 'Удалить';
  li.append(span, del);
  list.append(li);

  input.value = '';
  input.focus();
});

list.addEventListener('click', (event) => {
  const button = event.target.closest('.delete');
  if (button) button.closest('li').remove();
});

Открыть песочницу

Частые вопросы

Что такое DOM простыми словами?

Дерево объектов, в которое браузер превращает HTML. JavaScript меняет это дерево, и страница сразу перерисовывается.

Почему querySelector возвращает null?

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

Чем textContent отличается от innerHTML?

textContent вставляет текст как есть, innerHTML разбирает строку как HTML. Для данных пользователя используй textContent.

Как добавить элемент на страницу?

Создай его через document.createElement('li'), заполни textContent и вставь в родителя через append.

Почему обработчик не работает на новых элементах?

Подписка была сделана до их появления. Повесь обработчик на родителя и проверяй event.target через closest — это делегирование.

Источники

Практикум по теме