Урок 9. DOM: элементы страницы и события
Когда браузер открывает страницу, он превращает HTML в дерево объектов — DOM (Document Object Model, объектная модель документа). Каждый тег становится объектом-элементом: у него есть свойства (текст, классы, атрибуты) и методы. JavaScript меняет эти объекты, и браузер сразу перерисовывает страницу.
Всё в этом уроке работает только в браузере: в Node.js страницы и document нет. Примеры проверяйте в файле .html или прямо в консоли (F12) на открытой странице.
Где ставить скрипт
Скрипт может обращаться только к тем элементам, которые браузер уже прочитал. Если <script> стоит в <head> без особых пометок, он выполнится раньше, чем появится <body>, и элементов не найдёт. Надёжный способ — отдельный файл с атрибутом defer:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Счётчик</title>
<script src="counter.js" defer></script>
</head>
<body>
<p>Нажато: <span id="count">0</span></p>
<button id="plus">+1</button>
<button id="reset">Сбросить</button>
</body>
</html>defer велит загрузить файл сразу, а выполнить — когда вся страница прочитана. Если скрипт всё же выполнился слишком рано, в консоли появляется ошибка Cannot read properties of null — разбор в справке Ошибка Cannot read properties of null.
Найти элемент
const countEl = document.getElementById("count");
const firstButton = document.querySelector("button");
const plusBtn = document.querySelector("#plus");
const allButtons = document.querySelectorAll("button");
console.log(allButtons.length);document.getElementById("count")— элемент сid="count". Решётка здесь не нужна.document.querySelector(селектор)— первый элемент по селектору CSS:"button"— по тегу,"#plus"— поid,".menu"— по классу,"form input"— поле внутри формы.document.querySelectorAll(селектор)— все подходящие элементы. Результат похож на массив: у него естьlengthиforEach, его перебирают черезfor...of.
Если элемента нет, getElementById и querySelector возвращают null, а querySelectorAll — пустой список.
Изменить элемент
const title = document.querySelector("h1");
title.textContent = "Новый заголовок";
title.style.color = "darkblue";
title.classList.add("highlight");
title.classList.remove("hidden");
title.classList.toggle("active");
const link = document.querySelector("a");
link.href = "https://developer.mozilla.org/";
link.setAttribute("target", "_blank");textContent— текст внутри элемента. Присваивание заменяет весь текст.style.свойство— стиль прямо на элементе. Имена свойств CSS с дефисом пишутся в camelCase:background-color→style.backgroundColor.classList.add,remove,toggle— добавить, убрать, переключить класс. Обычно удобнее описать вид класса в CSS, а из кода только включать и выключать класс, чем задавать стили по одному.- Атрибуты меняют через одноимённые свойства (
href,src,value,disabled) или методомsetAttribute.
Есть ещё свойство innerHTML — оно принимает не текст, а HTML-разметку: list.innerHTML = "<li>Пункт</li>".
Не вставляйте через innerHTML то, что ввёл пользователь или прислал сервер. Если в тексте окажется <img src=x onerror="…">, браузер выполнит чужой код на вашей странице: так крадут входы и данные посетителей (атака XSS). Для текста всегда берите textContent — он показывает любые символы как текст.
Создать и удалить элемент
const list = document.querySelector("#list");
const item = document.createElement("li");
item.textContent = "Купить хлеб";
item.classList.add("todo");
list.append(item);
const old = document.querySelector(".obsolete");
if (old) {
old.remove();
}document.createElement("li") создаёт элемент, но на странице его ещё нет. append добавляет его в конец указанного элемента, prepend — в начало. remove() убирает элемент со страницы. Проверка if (old) защищает от ошибки, если такого элемента нет.
События
Событие — то, что происходит на странице: щелчок, нажатие клавиши, ввод в поле, отправка формы. Чтобы отреагировать, элементу назначают обработчик — функцию, которую браузер вызовет, когда событие случится. Вот файл counter.js для страницы-счётчика из начала урока:
const countEl = document.getElementById("count");
const plusBtn = document.getElementById("plus");
const resetBtn = document.getElementById("reset");
let count = 0;
plusBtn.addEventListener("click", () => {
count++;
countEl.textContent = count;
});
resetBtn.addEventListener("click", () => {
count = 0;
countEl.textContent = count;
});Откройте страницу: каждое нажатие «+1» увеличивает число на странице, «Сбросить» возвращает ноль.
addEventListener(событие, функция) — первый аргумент — имя события строкой, второй — обработчик. Функцию передают, а не вызывают: addEventListener("click", update), без скобок после update. Со скобками функция выполнится сразу, один раз, а обработчиком станет её результат.
Частые события:
| Событие | Когда происходит |
|---|---|
click | щелчок по элементу |
input | изменилось содержимое поля ввода |
change | поле изменено и потеряло фокус; выбран пункт списка или флажок |
submit | отправка формы |
keydown | нажата клавиша |
DOMContentLoaded | страница прочитана, элементы готовы (событие у document) |
Обработчик получает объект события. В нём — подробности: event.target — элемент, с которым случилось событие, event.key — нажатая клавиша.
Форма: список дел
Обычный пример — форма, которая добавляет пункты в список. Разметка:
<form id="todo-form">
<input id="todo-text" placeholder="Новое дело">
<button>Добавить</button>
</form>
<ul id="todo-list"></ul>Код:
const form = document.querySelector("#todo-form");
const input = document.querySelector("#todo-text");
const list = document.querySelector("#todo-list");
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = input.value.trim();
if (text === "") {
return;
}
const item = document.createElement("li");
item.textContent = text;
item.addEventListener("click", () => item.classList.toggle("done"));
list.append(item);
input.value = "";
input.focus();
});Введите дело и нажмите «Добавить» или Enter: в списке появится пункт, поле очистится. Щелчок по пункту включает и выключает у него класс done — в CSS ему можно задать, например, зачёркнутый текст.
- Кнопка внутри формы по умолчанию отправляет форму, а браузер при отправке перезагружает страницу.
event.preventDefault()отменяет это действие по умолчанию — страница остаётся на месте. input.value— текст в поле, всегда строкой;trim()убирает пробелы по краям. Подробнее — в справке Как получить значение input.- Обработчик на
submitформы, а не наclickкнопки, срабатывает и при нажатииEnterв поле.
Частые ошибки
nullвместо элемента — скрипт выполнился раньше, чем появился элемент, или селектор с опечаткой. Ставьтеdefer, проверяйтеid; вquerySelectorдляidнужна решётка:"#plus".addEventListenerу списка элементов. У результатаquerySelectorAllтакого метода нет — обработчик назначают каждому элементу в цикле.- Значение поля прочитано при загрузке.
const text = input.valueв начале скрипта даёт пустую строку: читать значение нужно внутри обработчика, в момент события. - Страница перезагружается при отправке формы — забыт
event.preventDefault().
Коротко
- DOM — страница в виде объектов; скрипт подключают с
defer, чтобы элементы уже были. - Найти:
querySelector,querySelectorAll,getElementById; нет элемента —null. - Изменить:
textContent,classList,style, атрибуты; создать —createElementиappend; удалить —remove. - События:
element.addEventListener("click", функция); у формы —submitиpreventDefault(). - Текст пользователя — только через
textContent, не черезinnerHTML.