Перейти к основному содержимому

Работа с HTML в JavaScript

Разработчику Архитектору

Работа с HTML в JavaScript

JavaScript обладает мощным инструментом для взаимодействия со структурой веб-страницы, который называется Document Object Model или сокращенно DOM. Этот механизм представляет собой дерево объектов, где каждый элемент HTML-кода становится объектом с набором свойств и методов. Скрипт может изменять внешний вид страницы, добавлять новые блоки, удалять существующие элементы и реагировать на действия пользователя без перезагрузки всей страницы.

Для успешной работы скрипта критически важно правильное расположение кода внутри файла. Браузер обрабатывает HTML-код сверху вниз последовательно. Если тег <script> расположен в верхней части документа, до того как браузер встретит описание элементов div, button или input, то попытка найти эти элементы вернет пустое значение. Это происходит потому, что к моменту выполнения скрипта соответствующие узлы дерева еще не созданы.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Пример ошибки</title>
<script>
const element = document.getElementById('myButton');
console.log(element); // Выведет null, так как кнопка еще не создана
</script>
</head>
<body>
<button id="myButton">Нажми меня</button>
</body>
</html>

В данном примере переменная element получит значение null. Попытка вызвать метод у этого значения вызовет ошибку выполнения. Чтобы избежать такой ситуации, разработчики размещают теги со скриптами в самом конце секции body. В этом случае браузер сначала полностью построит структуру страницы, создаст все элементы, и только после этого выполнит код, который сможет безопасно обратиться к уже существующим узлам.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Правильный порядок</title>
</head>
<body>
<button id="myButton">Нажми меня</button>

<script>
const element = document.getElementById('myButton');
console.log(element); // Выведет объект кнопки
</script>
</body>
</html>

Альтернативным подходом является использование атрибута defer у тега скрипта. Этот атрибут сообщает браузеру отложить выполнение кода до момента полной загрузки и парсинга всего HTML-документа. При этом скрипт выполняется асинхронно, не блокируя отображение контента.

<script src="main.js" defer></script>

Свойство document предоставляет доступ ко всем методам поиска элементов. Методы работают по принципу поиска в дереве DOM и возвращают конкретные объекты или коллекции объектов. Выбор способа поиска зависит от уникальности элемента и его назначения на странице.


Поиск по идентификатору (ID)

Идентификатор (id) — это уникальный атрибут, который должен присутствовать только один раз на всей странице. Он служит точкой входа для быстрого и надежного поиска конкретного элемента. Браузер использует внутренние оптимизации для мгновенного нахождения элемента с заданным ID.

Метод getElementById принимает строковое значение идентификатора и возвращает единственный найденный элемент. Если элемент с таким ID не найден, метод вернет null. Это наиболее производительный способ поиска среди всех доступных методов.

const headerElement = document.getElementById('pageHeader');
headerElement.style.color = 'blue';

В этом коде мы находим элемент с атрибутом id="pageHeader" и изменяем цвет его текста на синий. Если бы на странице было два элемента с одинаковым ID, метод все равно вернул бы первый найденный, но такое поведение считается ошибкой проектирования разметки.

<div id="mainTitle">Заголовок страницы</div>
<button onclick="changeColor()">Изменить цвет</button>

<script>
function changeColor() {
const title = document.getElementById('mainTitle');
if (title !== null) {
title.style.backgroundColor = 'yellow';
} else {
console.error('Элемент не найден');
}
}
</script>

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


Поиск по классу (Class)

Классы (class) используются для группировки элементов, которые имеют общие стили или поведение. В отличие от ID, один класс может быть присвоен множеству элементов на странице. Метод getElementsByClassName возвращает живую коллекцию всех элементов с указанным классом.

Коллекция работает как массив, но имеет свои особенности. Она не поддерживает методы массивов напрямую, такие как map или filter. Для работы с элементами нужно использовать цикл for или преобразовать коллекцию в настоящий массив. Коллекция обновляется автоматически при изменении структуры страницы, поэтому она называется живой.

const buttons = document.getElementsByClassName('action-btn');
console.log(buttons.length); // Количество кнопок с классом action-btn

for (let i = 0; i < buttons.length; i++) {
buttons[i].style.cursor = 'pointer';
}

В этом примере мы проходимся по всем кнопкам с классом action-btn и устанавливаем курсор в виде руки. Обратите внимание на индексацию: коллекция начинается с нуля, как и обычный массив.

<button class="action-btn">Первая кнопка</button>
<button class="action-btn">Вторая кнопка</button>
<button class="other-class">Другая кнопка</button>

<script>
const allButtons = document.getElementsByClassName('action-btn');

for (let btn of allButtons) {
btn.addEventListener('click', () => {
alert('Клик по кнопке!');
});
}
</script>

Здесь мы используем современный синтаксис цикла for...of, который позволяет перебирать элементы коллекции напрямую. Это делает код более читаемым и понятным.

Также существует метод querySelectorAll, который возвращает статическую NodeList (необновляемую коллекцию). Это часто предпочтительнее, когда работа идет с фиксированным набором элементов.

const staticList = document.querySelectorAll('.static-item');
// Список не изменится, даже если добавить новый элемент с классом .static-item

Поиск по тегу (Tag)

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

const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs.length); // Количество параграфов на странице

for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].classList.add('highlighted');
}

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

<p>Первый текст</p>
<p>Второй текст</p>
<div>Не параграф</div>

<script>
const texts = document.getElementsByTagName('p');

for (let p of texts) {
p.innerText += ' - обработано';
}
</script>

Здесь мы модифицируем содержимое каждого параграфа, добавляя к нему текст. Метод чувствителен к регистру, поэтому передача 'P' вместо 'p' не даст результата.


Поиск по селектору CSS

Метод querySelector является самым универсальным способом поиска элементов. Он принимает любой допустимый CSS-селектор и возвращает первый найденный элемент. Если элемент не найден, возвращается null. Этот метод работает быстрее и гибче, чем специализированные методы, так как использует встроенный движок парсинга CSS браузера.

const mainContent = document.querySelector('#main-content');
const firstLink = document.querySelector('a');
const nestedItem = document.querySelector('.container .item:first-child');

В первом примере мы ищем элемент с ID main-content. Во втором — первый ссылочный элемент на странице. В третьем — первый дочерний элемент с классом item, находящийся внутри контейнера .container.

<div class="wrapper">
<span class="target">Текст</span>
<span class="target">Еще текст</span>
</div>

<script>
const target = document.querySelector('.wrapper .target');
target.style.fontWeight = 'bold';
</script>

Здесь мы находим первый элемент с классом target, который находится внутри .wrapper. Если бы мы использовали querySelectorAll, то получили бы список всех таких элементов.

Метод querySelectorAll возвращает статический список всех совпадений. Он поддерживает сложные селекторы, включая псевдоклассы и атрибуты.

const inputs = document.querySelectorAll('input[type="text"]:not(:disabled)');
inputs.forEach(input => {
input.focus();
});

Этот код находит все текстовые поля, которые не заблокированы, и ставит фокус на каждое из них. Использование forEach возможно благодаря тому, что NodeList поддерживает этот метод.


Поиск по имени атрибута (Name)

Атрибут name часто используется в формах для идентификации полей ввода. Метод getElementsByName возвращает живую коллекцию всех элементов с указанным именем. Этот метод специфичен для форм и редко используется вне контекста обработки данных формы.

const emailFields = document.getElementsByName('email');
console.log(emailFields[0].value); // Значение первого поля

for (let field of emailFields) {
field.required = true;
}

В этом примере мы получаем все поля с именем email и делаем их обязательными для заполнения. Коллекция индексируется как массив, поэтому доступ к первому элементу осуществляется через [0].

<form id="userForm">
<input type="text" name="username" placeholder="Имя">
<input type="text" name="email" placeholder="Email">
<input type="text" name="email" placeholder="Повторите Email">
<button type="submit">Отправить</button>
</form>

<script>
const emails = document.getElementsByName('email');

for (let i = 0; i < emails.length; i++) {
emails[i].addEventListener('blur', function() {
if (this.value.length < 5) {
this.setCustomValidity('Email слишком короткий');
} else {
this.setCustomValidity('');
}
});
}
</script>

Здесь мы добавляем валидацию для всех полей с именем email. При потере фокуса проверяется длина введенного текста.


Свойства элементов

Каждый найденный элемент обладает набором свойств, позволяющих читать и изменять его содержимое и состояние. Основные свойства связаны с текстом и HTML-разметкой.

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

const title = document.getElementById('pageTitle');
console.log(title.innerText); // "Добро пожаловать"

title.innerText = "Новый заголовок";

Свойство textContent также возвращает текст, но включает текст из всех вложенных элементов, включая скрытые. Оно работает быстрее innerText, так как не требует анализа стилей. Это свойство идеально подходит для получения полного текстового содержимого узла.

const article = document.querySelector('article');
const fullText = article.textContent;
// Содержит текст даже из элементов style или script

Свойство innerHTML возвращает HTML-разметку внутри элемента как строку. Изменение этого свойства позволяет вставлять новый HTML-код, который будет интерпретирован браузером. Использование innerHTML требует осторожности, так как оно может привести к уязвимостям безопасности при работе с пользовательским вводом.

const container = document.getElementById('content');
console.log(container.innerHTML); // "<p>Текст</p><span>Еще</span>"

container.innerHTML = '<h2>Новый заголовок</h2><p>Новый контент</p>';

В этом примере мы полностью заменяем содержимое контейнера новым HTML-блоком. Браузер создает новые элементы на основе переданной строки.

Свойство outerHTML возвращает сам элемент вместе с его открывающим и закрывающим тегами. Это удобно для клонирования или замены всего блока.

const button = document.querySelector('button');
const buttonCode = button.outerHTML;
console.log(buttonCode); // "<button id='btn'>Нажми</button>"

Для управления стилями используется свойство style. Оно позволяет устанавливать CSS-свойства в формате camelCase (например, backgroundColor вместо background-color).

const box = document.getElementById('box');
box.style.width = '100px';
box.style.backgroundColor = '#3498db';
box.style.marginTop = '20px';

Свойство className позволяет менять классы элемента целиком. Установка нового класса заменяет все предыдущие классы.

const item = document.querySelector('.item');
item.className = 'active highlighted';
// Теперь у элемента два класса: active и highlighted

Для управления отдельными классами лучше использовать метод classList. Он предоставляет удобные методы для добавления, удаления и проверки наличия классов.

const card = document.querySelector('.card');
card.classList.add('expanded');
card.classList.remove('collapsed');
card.classList.toggle('hidden'); // Добавляет, если нет, убирает, если есть
console.log(card.classList.contains('active')); // true или false

Свойство value используется для элементов ввода (input, textarea, select). Оно содержит текущее значение поля.

const input = document.querySelector('#username');
const userName = input.value;

input.value = 'Гость'; // Устанавливаем новое значение

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


Модификация и создание элементов HTML в JavaScript

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

Работа с модификацией начинается с понимания того, что каждый элемент в DOM является объектом со своим набором свойств. Изменение этих свойств напрямую влияет на отображение элемента в браузере. Например, свойство innerText позволяет заменить текст внутри тега, а свойство style дает возможность менять цвет фона или размер шрифта программным путем. Эти операции выполняются очень быстро и обеспечивают плавный пользовательский опыт.


Изменение содержимого текстовых блоков

Самый частый сценарий использования — замена текста внутри элемента. Для этого служат свойства innerText, textContent и innerHTML. Каждое из них имеет свою специфику работы и область применения.

Свойство innerText возвращает видимый текст узла, игнорируя скрытые элементы и учитывая CSS-стили. При записи оно преобразует введенную строку в текстовые узлы, удаляя при этом старый текст. Это свойство учитывает отступы и переносы строк так, как они отображаются визуально. Если вы хотите изменить заголовок или сообщение на странице, это будет наиболее безопасным и понятным вариантом.

const header = document.getElementById('mainTitle');
header.innerText = 'Добро пожаловать в систему';

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

Свойство textContent работает аналогично, но возвращает весь текст, включая тот, который скрыт стилями (например, через display: none). Оно также быстрее, так как не требует анализа стилей для определения видимости. Использование textContent предпочтительно, когда нужно скопировать полное содержимое узла или вставить текст, не затрагивая HTML-разметку.

const article = document.querySelector('.content');
article.textContent = 'Это новый текст статьи, который не содержит HTML тегов.';

Здесь мы вставляем чистый текст в статью. Если бы мы использовали innerHTML, браузер мог бы попытаться интерпретировать специальные символы как код, что привело бы к ошибкам или уязвимостям безопасности.

Свойство innerHTML позволяет вставлять HTML-разметку внутрь элемента. Браузер парсит переданную строку и создает соответствующие дочерние узлы. Это мощный инструмент для создания сложных структур, но он требует осторожности при работе с данными от пользователей, так как может привести к внедрению вредоносного кода (XSS).

const container = document.getElementById('newsFeed');
container.innerHTML = '<div class="item"><h3>Новость</h3><p>Текст новости</p></div>';

В этом коде мы добавляем новый блок новостей внутрь контейнера. Строка <div> будет распарсена, и внутри container появится новый элемент div с заголовком и параграфом. Важно помнить, что использование innerHTML очищает все предыдущие дочерние элементы узла.

Для управления стилями используется свойство style. Оно принимает имя CSS-свойства в формате camelCase (первое слово с большой буквы, если их несколько) и значение.

const box = document.querySelector('.highlight-box');
box.style.backgroundColor = '#ffeb3b';
box.style.color = '#000000';
box.style.fontSize = '20px';
box.style.borderRadius = '10px';

Этот фрагмент кода меняет внешний вид блока, делая его желтым с черным текстом и скругленными углами. Стили применяются инлайн, то есть попадают непосредственно в атрибут style элемента в HTML.


Управление классами и внешним видом

Часто требуется не просто изменить один стиль, а переключить состояние элемента, например, добавить класс, отвечающий за активное состояние кнопки или скрытие блока. Для этого существует объект classList, который предоставляет удобные методы для работы с коллекцией классов.

Метод add() добавляет один или несколько классов к элементу. Если класс уже существует, метод не вызовет ошибок, а просто оставит текущее состояние.

const button = document.querySelector('#submitBtn');
button.classList.add('active', 'loading');
// Теперь у кнопки есть два класса: active и loading

Метод remove() удаляет указанные классы. Это полезно для сброса состояний после завершения действий.

const card = document.querySelector('.product-card');
card.classList.remove('selected');
// Класс selected больше не применяется к карточке товара

Метод toggle() действует как переключатель. Если класс присутствует, он его удаляет. Если класса нет, он добавляет. Это идеальный инструмент для реализации кнопок "показать/скрыть" или меню.

const menu = document.querySelector('.sidebar-menu');
menu.classList.toggle('open');
// Если меню было закрыто, оно откроется. Если открыто — закроется.

Метод contains() проверяет наличие конкретного класса и возвращает логическое значение true или false. Это удобно использовать для условной логики перед выполнением действий.

const item = document.querySelector('.list-item');
if (item.classList.contains('disabled')) {
console.log('Элемент заблокирован');
} else {
item.classList.add('disabled');
}

Альтернативой работе с отдельными методами является присвоение строки свойству className. Однако этот подход перезаписывает весь список классов, поэтому его следует использовать только тогда, когда нужно установить конкретный набор классов, полностью заменив предыдущий.

const nav = document.querySelector('nav');
nav.className = 'main-navigation dark-theme';
// Все старые классы удалены, остались только эти два

Добавление новых элементов в документ

Чтобы создать новую часть интерфейса, необходимо сначала создать сам элемент в памяти, а затем вставить его в дерево DOM. Процесс создания включает в себя генерацию объекта типа Element и последующую манипуляцию его свойствами.

Метод createElement() создает пустой элемент указанного тега. В этот момент элемент еще невидим для пользователя, так как он находится в оперативной памяти.

const newParagraph = document.createElement('p');
newParagraph.innerText = 'Этот параграф был создан скриптом';
newParagraph.classList.add('dynamic-content');

После создания элемента ему можно задать атрибуты, стили и содержимое. Затем используется метод appendChild() для помещения нового узла в конец списка детей родительского элемента.

const container = document.getElementById('textContainer');
container.appendChild(newParagraph);

Если нужно вставить элемент не в конец, а в конкретное место, используются методы insertBefore() или prepend() / append(). Метод prepend() добавляет элемент первым среди детей родителя.

const header = document.querySelector('header');
const logo = document.createElement('img');
logo.src = '/logo.png';
logo.alt = 'Логотип';
header.prepend(logo); // Логотип станет первым элементом в header

Метод insertAdjacentElement() позволяет гибко позиционировать элементы относительно другого узла. Он принимает четыре параметра: beforebegin, afterbegin, beforeend, afterend.

const list = document.querySelector('ul');
const newItem = document.createElement('li');
newItem.innerText = 'Новый пункт списка';
list.insertAdjacentElement('beforeend', newItem); // Добавляет в конец списка

Также существует метод cloneNode(), который создает копию существующего узла. Это эффективно, когда нужно добавить несколько одинаковых элементов, например, пункты списка или карточки товаров.

const templateItem = document.querySelector('.template-item');
for (let i = 0; i < 5; i++) {
const clone = templateItem.cloneNode(true); // true означает глубокий клон со всеми детьми
document.body.appendChild(clone);
}

Глубокий клон (true) копирует весь поддерево, включая вложенные элементы и их атрибуты. Поверхностный клон (false) копирует только сам элемент без содержимого.


Удаление элементов из структуры

Управление памятью и оптимизация верстки требуют возможности удаления ненужных узлов. Метод remove() вызывает удаление элемента из его родительского узла.

const oldMessage = document.getElementById('temp-message');
if (oldMessage) {
oldMessage.remove();
}

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

Метод replaceWith() заменяет текущий элемент на новый, передавая ему место в дереве. Новый элемент может быть другим узлом или даже строкой HTML, которая будет разобрана браузером.

const errorBlock = document.querySelector('.error-alert');
const successBlock = document.createElement('div');
successBlock.className = 'success-alert';
successBlock.innerText = 'Операция выполнена успешно';
errorBlock.replaceWith(successBlock);
// Блок ошибки исчезает, появляется блок успеха

Также можно использовать метод innerHTML = '' для очистки всего содержимого узла, но это удалит и все вложенные элементы, что иногда нежелательно.


Работа с атрибутами элементов

Атрибуты хранят дополнительную информацию об элементе, такую как ссылки, имена полей форм или статусы. Для доступа к ним используются методы getAttribute(), setAttribute() и свойство attributes.

Метод getAttribute(name) возвращает значение атрибута. Если атрибут отсутствует, возвращается null.

const link = document.querySelector('a');
const hrefValue = link.getAttribute('href');
console.log(hrefValue); // Выведет ссылку, например https://example.com

Метод setAttribute(name, value) устанавливает или изменяет значение атрибута. Если атрибут не существует, он создается.

const input = document.querySelector('#username');
input.setAttribute('placeholder', 'Введите ваше имя');
input.setAttribute('required', ''); // Атрибут required активируется

Существуют специальные свойства для часто используемых атрибутов, такие как src, href, value, checked. Они работают автоматически и синхронизируются с DOM.

const image = document.querySelector('.avatar');
image.src = '/new-avatar.jpg'; // Меняет источник изображения
image.alt = 'Новый аватар пользователя'; // Меняет альтернативный текст

Для проверки наличия атрибута используется метод hasAttribute().

const btn = document.querySelector('#action-btn');
if (btn.hasAttribute('disabled')) {
console.log('Кнопка недоступна');
}

Создание форм и обработка ввода

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

const formContainer = document.getElementById('form-area');
const newForm = document.createElement('form');
newForm.id = 'dynamicForm';
newForm.action = '/submit';
newForm.method = 'POST';

const label = document.createElement('label');
label.htmlFor = 'emailInput';
label.innerText = 'Email:';

const input = document.createElement('input');
input.type = 'email';
input.id = 'emailInput';
input.name = 'user_email';
input.required = true;

newForm.append(label, input);
formContainer.appendChild(newForm);

В этом примере мы собираем форму из отдельных частей. Метод append() добавляет несколько узлов сразу. Свойство htmlFor связывает лейбл с полем ввода, улучшая доступность.


Пример комплексного взаимодействия

Рассмотрим сценарий, где скрипт реагирует на клик по кнопке, создает новый элемент, меняет его класс и добавляет в список.

const addBtn = document.querySelector('.add-item-btn');
const list = document.querySelector('.task-list');

addBtn.addEventListener('click', function() {
const taskText = prompt('Введите название задачи:');

if (!taskText) return;

const li = document.createElement('li');
li.className = 'task-item pending';
li.innerText = taskText;

const deleteBtn = document.createElement('button');
deleteBtn.innerText = 'Удалить';
deleteBtn.className = 'delete-btn';

deleteBtn.addEventListener('click', function() {
li.remove();
});

li.appendChild(deleteBtn);
list.appendChild(li);
});

При клике на кнопку вызывается событие, которое запрашивает ввод у пользователя. Создается элемент списка li, добавляется кнопка удаления с собственным обработчиком событий. Кнопка удаления при клике вызывает метод remove() для своего родительского элемента. Весь блок добавляется в список задач.


Особенности производительности и оптимизации

Частое изменение DOM может замедлить работу страницы, так как браузер вынужден пересчитывать макет и перерисовывать экран. Для оптимизации рекомендуется накапливать изменения в памяти, используя DocumentFragment, а затем вставлять их в документ одним действием.

DocumentFragment — это легкий контейнер, который не отображается на странице. Элементы, добавленные в него, не вызывают перерисовку до момента их переноса в основной документ.

const fragment = document.createDocumentFragment();
const ul = document.querySelector('.items');

for (let i = 1; i <= 100; i++) {
const li = document.createElement('li');
li.innerText = 'Элемент ' + i;
fragment.appendChild(li);
}

ul.appendChild(fragment);
// Браузер выполнит одну перерисовку вместо ста

Этот подход значительно повышает производительность при создании больших списков или таблиц. Также стоит минимизировать количество обращений к DOM внутри циклов, сохраняя результаты поиска в переменные.

Изменение стилей лучше группировать или использовать CSS-классы, чтобы избежать частых операций чтения и записи свойств стиля, которые могут вызвать перерисовку (reflow). Переключение классов через classList обычно выполняется эффективнее, чем прямое изменение множества стилей в цикле.