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

узлы≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Всё, что вы видите на странице, кто-то создал: либо парсер, разобравший HTML-файл, либо ваш код. document.createElement — второй путь: элемент рождается в памяти, пустым и невидимым, и ждёт, пока его вставят в документ.

Разделы темы:·

Метод принимает имя тега строкой и возвращает настоящий DOM-узел: с полным набором свойств, методов и будущих обработчиков. Внутри у него пусто, родителя нет, на экране его тоже нет — но это уже полноценный <button>, а не заготовка.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Регистр имени тега не важен: createElement("DIV") и createElement("div") дают один и тот же элемент. Пишите строчными — так принято, а tagName всё равно вернёт заглавные буквы, это наследие HTML.

Ошибки не будет: браузер создаст HTMLUnknownElement — узел без своего поведения, но вставляемый и стилизуемый. А вот SVG так не сделать: createElement всегда работает в пространстве имён HTML, для графики нужен createElementNS.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Дальше элемент обычный: textContent кладёт внутрь текст, className и id — свойства, setAttribute и dataset — всё остальное. Порядок не важен, всё это происходит в памяти, до всякой вставки.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Соблазн написать innerHTML вместо всего этого велик, и разница принципиальна: textContent кладёт строку как текст, innerHTML разбирает её как разметку. Одна и та же строка даёт в первом случае ноль дочерних элементов, во втором — настоящий тег.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Присваивание innerHTML не правит существующие узлы, а выбрасывает их и парсит содержимое заново. Внешне разметка та же, но узлы другие — вместе со старыми узлами исчезают их обработчики и любые ссылки на них в вашем коде.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Текст в DOM живёт не «внутри» элемента абстрактно, а в отдельном узле — Text. Обычно его создают за вас: присвоили textContent — браузер сам сделал текстовый узел. Но его можно собрать и руками: document.createTextNode возвращает узел с nodeType 3, без тега и без атрибутов.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Главное свойство такого узла: строка в нём остаётся строкой навсегда. Что бы вы туда ни положили — угловые скобки, амперсанды, кусок чужого HTML, — это будет показано как символы. Когда данные пришли от пользователя или из сети, это ровно то, что нужно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера
СпособЧто делает со строкойКогда брать
elem.textContent = sЗаменяет всё содержимое одним текстовым узломПочти всегда, когда нужен текст
elem.append(s)Добавляет текстовый узел в конец, не трогая остальноеКогда текст соседствует с элементами
document.createTextNode(s)Возвращает узел, вставку делаете самиКогда узел нужен как значение: сохранить, переместить
elem.innerHTML = sРазбирает строку как разметкуТолько для разметки, которую написали вы сами

Созданный элемент не принадлежит документу. Он существует как значение в переменной, у него нет родителя, его не найдёт querySelector, и браузеру нечего рисовать: на экран попадает только то, что висит в дереве документа. Такой узел называют отсоединённым (detached), и проверяется это одним свойством — isConnected.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

У отсоединённого узла нет и геометрии: он нигде не расположен, поэтому все измерения дают нули, а правила CSS к нему не применяются — каскаду не от чего считать, у элемента нет предков в документе.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Отсюда классическая ошибка: создали элемент, тут же померили ширину, получили 0 и полчаса ищете виноватого в CSS. Мерить можно только после вставки — до неё мерить попросту нечего.

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Формально ничто не запрещает вставить пустой <ul> первым и дополнять его уже в документе — результат будет тот же. Но привычка «сначала собрал, потом вставил» бесплатна, а выигрывает всегда. О самих способах вставки — append, prepend, before, after — подробно в следующей статье.

  • document.createElement("div") возвращает полноценный элемент, живущий в памяти: без родителя, без места в документе и без пикселей на экране.
  • Наполняют его как обычный узел — textContent, className, setAttribute; innerHTML отличается тем, что разбирает строку как разметку и пересобирает содержимое заново.
  • createTextNode делает текстовый узел (nodeType 3) отдельным значением: строка в нём навсегда остаётся текстом, что и нужно для чужих данных.
  • Пока isConnected равен false, элемент не найдётся селектором и не имеет размеров — измерять его бессмысленно до вставки.
  • Собирать поддерево целиком и вставлять одним вызовом дешевле, чем добавлять узлы по одному в живую страницу.

1/3Элемент создан через createElement, но ещё не вставлен. Что вернёт его parentNode?

2/3Что выведет console.log(p.children.length) после p.textContent = "<b>привет</b>"?

3/3Зачем нужен document.createTextNode, если есть textContent?

Соберите карточку

Напишите функцию createCard(title, text), которая создаёт и возвращает структуру <div class="card"><h3>заголовок</h3><p>текст</p></div>. Внутрь функции вставка в документ не входит — она только собирает узел. Проверьте, что до вставки карточка не найдена на странице, а после — найдена.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Собираем три элемента и связываем их до вставки: заголовок и абзац кладём в контейнер, контейнер возвращаем наружу.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

append принимает сразу несколько узлов, поэтому заголовок и абзац добавляются одним вызовом. Функция ничего не знает о месте вставки — за это отвечает вызывающий код, и такую функцию проще переиспользовать.

Покажите разметку как текст

В переменной userInput лежит строка, пришедшая от пользователя. Выведите её в <div id="log"> так, чтобы теги внутри строки остались видимыми символами, а не превратились в элементы. Проверьте результат по числу дочерних элементов.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Любой из двух путей годится — важно лишь не отдавать строку парсеру:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Короче — log.textContent = userInput: результат тот же, браузер сам создаст текстовый узел. Ошибочный вариант здесь один — innerHTML: он разберёт строку как разметку, и в документе появится настоящий <img> с битым адресом.