DOM и события
Финальный аккорд: JavaScript управляет страницей — находит элементы, меняет их и реагирует на действия пользователя.
Что такое DOM
DOM (Document Object Model) — древовидная модель документа. Браузер превращает HTML в дерево объектов, а JavaScript умеет ходить по этому дереву, читать и менять его. Любая «живая» страница — это работа с DOM.
Поиск элементов
const h1 = document.querySelector("h1"); // первый совпавший
const cards = document.querySelectorAll(".card"); // все
const btn = document.getElementById("save"); // по id
console.log(h1.textContent); // читаем текст
Изменение элементов
const h1 = document.querySelector("h1");
h1.textContent = "Новый заголовок";
const card = document.querySelector(".card");
card.style.backgroundColor = "#1e293b";
card.classList.add("active"); // или remove / toggle
textContent — безопасный текст, innerHTML подставляет HTML-код. Используйте innerHTML аккуратно: если подставляете пользовательский ввод, есть риск XSS-атак.События
Событие — сигнал «что-то произошло». Слушаем клики:
const btn = document.querySelector("#likeBtn");
btn.addEventListener("click", () => {
likes++;
document.querySelector("#likeCount").textContent = likes;
});
Популярные события: click, input, submit, keydown, scroll, mouseover.
Живой пример: счётчик кликов
Полный интерактив — тот же код, что и выше, уже живёт здесь:
Создание элементов
Можно строить элементы программно и добавлять их в документ:
const li = document.createElement("li");
li.textContent = "Новый пункт";
list.appendChild(li);
list.innerHTML = ""; // очистить список
Финальный мини-проект
Соберите всё вместе — маленький «todo-список»:
- Поле ввода и кнопка «Добавить».
- По клику — создать элемент
<li>и добавить в список. - Клик по элементу — перечёркивать его (класс
.done). - Использовать
event.preventDefault()на форме, чтобы страница не перезагружалась.
const input = document.querySelector("#task");
const list = document.querySelector("#list");
form.addEventListener("submit", (e) => {
e.preventDefault(); // без перезагрузки
const li = document.createElement("li");
li.textContent = input.value;
li.addEventListener("click", () => li.classList.toggle("done"));
list.appendChild(li);
input.value = "";
});
Путешествие по дереву DOM
DOM — дерево, поэтому от любого элемента можно гулять к соседям: родителю, детям, братьям и сёстрам:
const el = document.querySelector(".card");
el.parentElement; // родитель
el.children; // массив прямых детей
el.firstElementChild; // первый ребёнок
el.lastElementChild; // последний ребёнок
el.nextElementSibling; // следующий сосед
el.previousElementSibling; // предыдущий сосед
parentElement: нашли кнопку, у неё поднялись к карточке — и уже её правим.Клавиатура и ввод: событие input
Событие input срабатывает на каждое изменение текста, а keydown ловит нажатия клавиш:
const search = document.querySelector("#search");
const hint = document.querySelector("#count");
search.addEventListener("input", () => {
hint.textContent = search.value.length + " симв.";
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal(); // ESC закрывает окно
});
e.key — имя клавиши («Enter», «Escape», «ArrowUp»). Так делают «живой счётчик символов», перенос фокуса и горячие клавиши.Делегирование событий
Один слушатель на родителя вместо сотни на детей — событие «всплывает» вверх, и мы ловим его один раз:
document.querySelector(".todo-list").addEventListener("click", (e) => {
const item = e.target.closest(".todo-item");
if (item) item.classList.toggle("done");
});
localStorage — память браузера
Данные можно сохранить между визитами без сервера. Внутри хранятся только строки, поэтому объекты сериализуем через JSON:
const theme = { dark: true };
localStorage.setItem("theme", JSON.stringify(theme)); // сохранить
const saved = JSON.parse(localStorage.getItem("theme")); // прочитать
localStorage.removeItem("theme"); // удалить
classList и data-атрибуты
Классами управляют через classList, а свои данные читают через dataset — модернизированный способ работы с data-*:
const box = document.querySelector(".box");
box.classList.add("active"); // добавить
box.classList.remove("active"); // убрать
box.classList.toggle("active"); // переключить
box.classList.contains("active"); // проверить
<div data-id="42">...</div>
box.dataset.id; // "42" — data-id превращается в dataset.id
.selected, не меняя остальных.Таймеры: setTimeout и setInterval
JS умеет откладывать запуск кода во времени — без сервера и без блокировки страницы:
const timer = setTimeout(() => {
console.log("через 3 секунды");
}, 3000);
clearTimeout(timer); // отменить, если передумали
const tick = setInterval(() => {
seconds++;
}, 1000); // каждую секунду
clearInterval(tick); // остановить
Стили и атрибуты через JS
Кроме классов, в DOM можно править стили и атрибуты напрямую — иногда это проще:
const el = document.querySelector(".card");
el.style.width = "300px"; // инлайн-стиль
el.setAttribute("title", "Помощь");
el.removeAttribute("disabled");
el.getAttribute("data-tooltip"); // просто прочитать
style используют в анимациях. Для логики — лучше классы: стили остаются «в одном месте» и не размазаны по JS.fetch: получение данных с сервера
Браузерный fetch запрашивает данные по API. Ответ обрабатывается цепочкой .then() — она приходит не сразу, поэтому код «продолжается» по готовности:
fetch("https://api.example.com/users")
.then(res => res.json())
.then(users => {
displayUsers(users);
})
.catch(err => {
console.error("Сеть упала:", err);
});
async/await — тоньше и ближе к обычному коду. Но с цепочками .then() начинают такой стиль понимать.Несколько обработчиков на одном элементе
addEventListener не заменяет старый обработчик — он добавляет ещё один:
const btn = document.querySelector("#green");
btn.addEventListener("click", () => console.log("1-й слушатель"));
btn.addEventListener("click", () => console.log("2-й слушатель"));
// сработают оба, по порядку
once и removeEventListener
Реакция один раз или отписка, когда обработчик больше не нужен:
btn.addEventListener("click", handler, { once: true });
// или снятие вручную (handler должна быть та же функция):
btn.removeEventListener("click", handler);
submit и preventDefault
Форма при отправке сама перезагружает страницу. Обычно мешает:
form.addEventListener("submit", (e) => {
e.preventDefault(); // отменяем перезагрузку
console.log("Отправлено без перезагрузки");
});
Остановка всплытия: stopPropagation
События «всплывают» от кликнутого элемента к предкам — это иногда мешает:
btn.addEventListener("click", (e) => {
e.stopPropagation(); // предки не узнают о клике
e.preventDefault(); // и браузерного действия не будет
});
Клавиатура: keydown, keyup
Горячие клавиши и поля проверяем по ключу кода:
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal();
if (e.code === "Enter") send();
});
e.key — символ («Enter», «я»), e.code — физическая клавиша (KeyW). Физику используйте для раскладок, символ для игр по букве.getBoundingClientRect: координаты
Точное положение элемента относительно окна — для всплывашек и анимаций:
const btnRect = btn.getBoundingClientRect();
btnRect.top; // от верхней кромки окна
btnRect.left;
btnRect.width; // ширина с учётом масштаба
insertAdjacentHTML: вставка куском
Рядом с элементом или вовнутрь можно вставить готовую HTML-строку:
const box = document.querySelector(".box");
box.insertAdjacentHTML("beforeend", "<span>конец списка</span>");
box.insertAdjacentHTML("afterbegin", "<span>начало</span>");
box.insertAdjacentHTML("beforebegin", "<p>перед блоком</p>");
Удаление узлов
Убрать элемент из документа проще простого:
const card = document.querySelector(".card");
card.remove(); // сам себя
parent.removeChild(card); // удаляет указанный ребёнок
closest: подъём по дереву
Ищем ближайшего предка с селектором (включая сам элемент):
const input = document.querySelector("input");
const form2 = input.closest("form"); // форма, если есть
const box = input.closest(".box");
FormData: форма в объект
Собрать все поля формы разом:
form.addEventListener("submit", (e) => {
e.preventDefault();
const data = new FormData(form);
console.log(data.get("name")); // значение поля name
console.log(Object.fromEntries(data));
});
scroll: событие и позиция
Следить за прокруткой, знать, насколько ушли вниз:
window.addEventListener("scroll", () => {
const scrolled = window.scrollY; // сколько пикселей после верха
if (scrolled > 200) header.classList.add("compact");
});
resize: размеры окна
Ловим повороты и ресайзы — например, переключать режим:
window.addEventListener("resize", () => {
const wide = window.innerWidth > 768;
document.body.classList.toggle("desktop", wide);
});
Живые и статические коллекции
querySelectorAll — статичный список, getElementsByClassName — живой:
const items1 = document.querySelectorAll("li"); // статическая
const items2 = document.getElementsByTagName("li"); // живая
list.append(document.createElement("li"));
items1.length; // не изменилось
items2.length; // изменилось — она «живая»
Инлайн-стили: style и setProperty
Точные значения пишутся прямо в style — например, анимации и метрики:
el.style.transform = "translateX(40px)";
el.style.width = `${w}px`;
// CSS-переменная — через setProperty:
el.style.setProperty("--gap", "12px");
classList.Что дальше
Вы прошли весь курс! Теперь стоит идти к большой практике:
- Возьмите макет из Figma — сверстайте его на HTML/CSS.
- Добавьте JS: меню бургер, слайдер, фильтрация товаров.
- Выложите сайт бесплатно на Netlify или GitHub Pages.
- После основ идите к фреймворкам: React или Vue.
Дерево страницы: элементы и тексты
Браузер превращает HTML в «дерево» объектов — DOM: каждый тег — узел, каждый узел — объект с родителем и детьми. Скрипт «поднимается» и «спускается» по этим веткам.
<body>
<h1>Заголовок</h1>
<p>Абзац</p>
</body>
// дерево: body → (h1, p)
Выбор одного: querySelector
document.querySelector("селектор") возвращает ПЕРВЫЙ подходящий элемент. Селектор — как в CSS: тег, класс, id, комбинация.
const title = document.querySelector("h1");
const btn = document.querySelector(".btn");
console.log(title.textContent);
Выбор многих: querySelectorAll
document.querySelectorAll(".item") — список ВСЕХ совпадений (NodeList). Его можно «обойти» циклом или forEach, но есть и подвох (живые коллекции — в конце курса).
const items = document.querySelectorAll(".item");
for (const el of items) {
el.classList.add("highlight");
}
Текст элемента: textContent
el.textContent читает/пишет весь текст внутри, включая «вложенные» узлы. Замена «как строка» — главный способ «перерисовать» тексты.
const el = document.querySelector("#title");
el.textContent = "Новый заголовок";
Переключение класса: classList.toggle
el.classList.toggle("open") — добавить класс, если его нет, и убрать, если есть. Класс «open» в CSS делает остальное: панель, аккордеон, меню.
menuBtn.addEventListener("click", () => {
menu.classList.toggle("open");
});
Спрятать и показать: скрытие через класс
Три способа: класс display, атрибут hidden, свойство style. Лучший на старте — класс в CSS (.hide { display: none }) и «перекидывание» его через classList.
.hide { display: none }
hint.classList.add("hide");
hint.classList.remove("hide");
Атрибуты: get/setAttribute
Управлять атрибутами можно напрямую: getAttribute, setAttribute, removeAttribute. Полезно для ссылок, картинок (src), title, «дата-атрибутов».
const img = document.querySelector("img");
img.setAttribute("src", "photo.jpg");
console.log(img.getAttribute("title"));
Соседи и родители: шаги по дереву
Из элемента можно «шагнуть»: .parentElement (вверх), .children (дети), .nextElementSibling / .previousElementSibling (соседи). Полезно, когда вокруг «нерасмеченный» HTML.
const item = document.querySelector(".item");
const parent = item.parentElement;
const next = item.nextElementSibling;
Практика: изменить заголовок по клику
Первая «интерактивность»: кнопка меняет заголовок. Три шага: найти кнопку, повесить слушатель, в обработчике поменять textContent. Всё — и консоль, DevTools, страница.
const title = document.querySelector("#greet");
const btn = document.querySelector("#go");
btn.addEventListener("click", () => {
title.textContent = "Вы нажали!";
});
Событие как «сигнал»
Клик, наведение, ввод текста, загрузка — браузер порождает события: «что-то произошло». JS «слушает» и реагирует. Это и есть интерактивность: страница отзывается на действия.
addEventListener: встать на прослушку
el.addEventListener("click", обработчик) — «при нажатии выполни функцию». Обработчик — любая функция: заранее описанная или стрелочная прямо внутри.
button.addEventListener("click", () => {
console.log("Клик!");
});
Несколько слушателей: не перезаписать
addEventListener можно повесить несколько раз — все сработают по очереди. Старый вариант onclick = function «перезаписывал»: последний съедал предыдущих.
btn.addEventListener("click", fnA);
btn.addEventListener("click", fnB);
// сработают оба — не «затёрлись»
Отмена поведения: preventDefault
Некоторые события имеют «поведение браузера»: клик по ссылке — переход, отправка формы — перезагрузка. event.preventDefault() останавливает его, оставляя «наш» код.
link.addEventListener("click", (ev) => {
ev.preventDefault();
console.log("ссылка нажата, но не ушли");
});
Событие формы: когда нажата отправка
Форма «отправляется» событием submit: ловим его на самой форме, делаем preventDefault, читаем поля. Так страница никуда не перезагружается.
form.addEventListener("submit", (ev) => {
ev.preventDefault();
const value = input.value.trim();
console.log("Отправлено:", value);
});
Клавиши: как поймать нажатие
keydown/keyup слушают клавиатуру; у события есть .key ("Enter", "ArrowDown") и .code (физическая клавиша). Быстрые клавиши, игры, «горячие» сочетания — отсюда.
document.addEventListener("keydown", (ev) => {
if (ev.key === "Escape") closeModal();
});
Мышь и палец: click и компания
Кроме click есть mouseenter (наведение), mouseleave (уход), dblclick, и touch-аналоги для телефонов. Общее правило: «клик» работает и мышью, и пальцем — стройте на нём.
card.addEventListener("mouseenter", () => {
card.classList.add("hover");
});
Объект события: event.target
В обработчике всегда доступен параметр-событие: ev.target — элемент, который «пустил» событие. querySelector находим «по имени», target — «по факту клика».
list.addEventListener("click", (ev) => {
console.log(ev.target.textContent);
});
Фокусный приём: делегирование в списках
Вешать обработчик на КАЖДЫЙ пункт списка дорого: делегирование ставит один слушатель на контейнер, а «кто именно» — решает ev.target + closest(). Список дел, меню, таблицы — классика.
todoList.addEventListener("click", (ev) => {
const li = ev.target.closest(".task");
if (li) li.classList.toggle("done");
});
Создание элемента: createElement
document.createElement("li") создаёт узел «в воздухе» — пока его нет в дереве. Наполняем (классы, текст), потом вставляем в документ. Так списки и карточки появляются «из скрипта».
const li = document.createElement("li");
li.textContent = "Новая задача";
li.classList.add("task");
Вставить в документ: append
parent.append(node) кладёт узел в конец родителя. Для «первого места» — prepend; append может принять сразу несколько аргументов. Новый узел «физически» появляется на странице.
todoList.append(li);
todoList.prepend(firstLi);
Горячая замена: innerHTML
el.innerHTML = "..." переписывает содержимое HTML-строкой — быстро «для своего кода», но опасно с недоверенными данными (вставился пользовательский HTML — выполнятся вложенные техники).
// для списка «своих» карточек — ничего лишнего
list.innerHTML = "<li>Пункт</li>";
Удаление: remove()
element.remove() убирает узел и всё его содержимое из дерева. «Корзина» в списке дел, скрытие всплывающих окон, очистка сообщений.
const task = document.querySelector(".task");
task.remove();
Поиск по data-атрибуту: dataset
Атрибуты data-* — «кармашки для данных разметки»: data-id, data-status. В JS к ним дорога через .dataset: el.dataset.id. Храните маленькие индентификаторы прямо в HTML.
<button data-action="delete">x</button>
const act = btn.dataset.action; // "delete"
Отложенная работа: setTimeout
setTimeout(fn, ms) запустит функцию через миллисекунды; setInterval — повторно по расписанию. Анимации-«по таймеру», всплывающие подсказки, обновление часов.
setTimeout(() => {
toast.classList.add("show");
}, 500);
Хранение между визитами: localStorage
localStorage памяти браузера держит строки «навсегда» (до очистки): настройки, заготовки, счётчики. Работа: setItem / getItem / removeItem. JSON.stringify объекты «складывать строчкой».
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme");
Практика: список дел
Всё вместе: форма → событие submit, createElement+append добавляют пункт, делегирование click гасит выполненное, localStorage помнит список между перезагрузками. Это «мини-проект» курса.
form.addEventListener("submit", (ev) => {
ev.preventDefault();
const li = document.createElement("li");
li.textContent = input.value.trim();
todoList.append(li);
input.value = "";
});
Итоги урока 9: собран мини-проект
Вы умеете находить элементы, слушать события, создавать/удалять узлы, делегировать, хранить данные. Лист дел, табы, счётчик, «живая» форма — за вами. Дальше — «Что дальше»: fetch, анимации, современные фреймворки.
Общая точка: document в JS
document — «вход» в страницу из JavaScript: doc-for поиска, создания и слушания. Код в