Javascript добавление элемента dom
- Переводы, 17 февраля 2019 в 13:11
- Сергей Ринг
Как правило, когда нужно выполнить какие-либо действия с DOM, разработчики используют jQuery. Однако практически любую манипуляцию с DOM можно сделать и на чистом JavaScript с помощью его DOM API.
Рассмотрим этот API более подробно:
В конце вы напишете свою простенькую DOM-библиотеку, которую можно будет использовать в любом проекте.
DOM-запросы
В материале представлены основы JavaScript DOM API. Все подробности и детали доступны на Mozilla Developer Network.
DOM-запросы осуществляются с помощью метода .querySelector() , который в качестве аргумента принимает произвольный СSS-селектор.
Он вернёт первый подходящий элемент. Можно и наоборот — проверить, соответствует ли элемент селектору:
Если нужно получить все элементы, соответствующие селектору, используйте следующую конструкцию:
Если же вы знаете, на какой родительский элемент нужно сослаться, можете просто проводить поиск среди его дочерних элементов, вместо того чтобы искать по всему коду:
Возникает вопрос: зачем тогда использовать другие, менее удобные методы вроде .getElementsByTagName() ? Есть маленькая проблема — результат вывода .querySelector() не обновляется, и когда мы добавим новый элемент (смотрите раздел 5), он не изменится.
Также querySelectorAll() собирает всё в один список, что делает его не очень эффективным.
Как работать со списками?
Вдобавок ко всему у .querySelectorAll() есть два маленьких нюанса. Вы не можете просто вызывать методы на результаты и ожидать, что они применятся к каждому из них (как вы могли привыкнуть делать это с jQuery). В любом случае нужно будет перебирать все элементы в цикле. Второе — возвращаемый объект является списком элементов, а не массивом. Следовательно, методы массивов не сработают. Конечно, есть методы и для списков, что-то вроде .forEach() , но, увы, они подходят не для всех случаев. Так что лучше преобразовать список в массив:
У каждого элемента есть некоторые свойства, ссылающиеся на «семью».
Поскольку интерфейс элемента ( Element ) унаследован от интерфейса узла ( Node ), следующие свойства тоже присутствуют:
Первые свойства ссылаются на элемент, а последние (за исключением .parentElement ) могут быть списками элементов любого типа. Соответственно, можно проверить и тип элемента:
Добавление классов и атрибутов
Добавить новый класс очень просто:
Добавление свойства для элемента происходит точно так же, как и для любого объекта:
Можно использовать методы .getAttibute() , .setAttribute() и .removeAttribute() . Они сразу же поменяют HTML-атрибуты элемента (в отличие от DOM-свойств), что вызовет браузерную перерисовку (вы сможете увидеть все изменения, изучив элемент с помощью инструментов разработчика в браузере). Такие перерисовки не только требуют больше ресурсов, чем установка DOM-свойств, но и могут привести к непредвиденным ошибкам.
Как правило, их используют для элементов, у которых нет соответствующих DOM-свойств, например colspan . Или же если их использование действительно необходимо, например для HTML-свойств при наследовании (смотрите раздел 9).
Добавление CSS-стилей
Добавляют их точно так же, как и другие свойства:
Какие-то определённые свойства можно задавать используя .style , но если вы хотите получить значения после некоторых вычислений, то лучше использовать window.getComputedStyle() . Этот метод получает элемент и возвращает CSSStyleDeclaration, содержащий стили как самого элемента, так и его родителя:
Изменение DOM
Можно перемещать элементы:
Если не хочется перемещать, но нужно вставить копию, используем:
Метод .cloneNode() принимает булевое значение в качестве аргумента, при true также клонируются и дочерние элементы.
Конечно, вы можете создавать новые элементы:
А затем вставлять их как было показано выше. Удалить элемент напрямую не получится, но можно сделать это через родительский элемент:
Можно обратиться и косвенно:
Методы для элементов
У каждого элемента присутствуют такие свойства, как .innerHTML и .textContent , они содержат HTML-код и, соответственно, сам текст. В следующем примере изменяется содержимое элемента:
На самом деле изменение HTML — плохая идея, т. к. теряются все изменения, которые были сделаны ранее, а также перегружаются обработчики событий. Лучше использовать такой способ только полностью отбросив весь HTML и заменив его копией с сервера. Вот так:
Однако это повлечёт за собой две перерисовки в браузере, в то время как .innerHTML приведёт только к одной. Обойти это можно, если сначала добавить всё в DocumentFragment, а затем добавить нужный вам фрагмент:
Обработчики событий
Один из самых простых обработчиков:
Но, как правило, его следует избегать. Здесь .onclick — свойство элемента, и по идее вы можете его изменить, но вы не сможете добавлять другие обработчики используя ещё одну функцию, ссылающуюся на старую.
Для добавления обработчиков лучше использовать .addEventListener() . Он принимает три аргумента: тип события, функцию, которая будет вызываться всякий раз при срабатывании, и объект конфигурации (к нему мы вернёмся позже).
Свойство event.target обращается к элементу, за которым закреплено событие.
А так вы сможете получить доступ ко всем свойствам:
Предотвращение действий по умолчанию
Для этого используется метод .preventDefault() , который блокирует стандартные действия. Например, он заблокирует отправку формы, если авторизация на клиентской стороне не была успешной:
Метод .stopPropagation() поможет, если у вас есть определённый обработчик события, закреплённый за дочерним элементом, и второй обработчик того же события, закреплённый за родителем.
Как говорилось ранее, метод .addEventListener() принимает третий необязательный аргумент в виде объекта с конфигурацией. Этот объект должен содержать любые из следующих булевых свойств (по умолчанию все в значении false ):
- capture: событие будет прикреплено к этому элементу перед любым другим элементом ниже в DOM;
- once: событие может быть закреплено лишь единожды;
- passive: event.preventDefault() будет игнорироваться (исключение во время ошибки).
Наиболее распространённым свойством является .capture , и оно настолько распространено, что для этого существует краткий способ записи: вместо того чтобы передавать его в объекте конфигурации, просто укажите его значение здесь:
Обработчики удаляются с помощью метода .removeEventListener() , принимающего два аргумента: тип события и ссылку на обработчик для удаления. Например свойство once можно реализовать так:
Наследование
Допустим, у вас есть элемент и вы хотите добавить обработчик событий для всех его дочерних элементов. Тогда бы вам пришлось прогнать их в цикле, используя метод myForm.querySelectorAll(‘input’) , как было показано выше. Однако вы можете просто добавить элементы в форму и проверить их содержимое с помощью event.target .
И ещё один плюс данного метода заключается в том, что к новым дочерним элементам обработчик будет привязываться автоматически.
Анимация
Проще всего добавить анимацию используя CSS со свойством transition . Но для большей гибкости (например для игры) лучше подходит JavaScript.
Вызывать метод window.setTimeout() , пока анимация не закончится, — не лучшая идея, так как ваше приложение может зависнуть, особенно на мобильных устройствах. Лучше использовать window.requestAnimationFrame() для сохранения всех изменений до следующей перерисовки. Он принимает функцию в качестве аргумента, которая в свою очередь получает метку времени:
Таким способом достигается очень плавная анимация. В своей статье Марк Браун рассуждает на данную тему.
Пишем свою библиотеку
Тот факт, что в DOM для выполнения каких-либо операций с элементами всё время приходится перебирать их, может показаться весьма утомительным по сравнению с синтаксисом jQuery $(‘.foo’).css() . Но почему бы не написать несколько своих методов, облегчающую данную задачу?
Теперь у вас есть своя маленькая библиотека, в которой находится всё, что вам нужно.
Здесь находится ещё много таких помощников.
Пример использования
Заключение
Теперь вы знаете, что для реализации простого модального окна или навигационного меню не обязательно прибегать к помощи сторонних фреймворков. Ведь в DOM API это уже всё есть, но, конечно, у данной технологии есть и свои минусы. Например всегда приходится вручную обрабатывать списки элементов, в то время как в jQuery это происходит по щелчку пальцев.
На этом уроке мы научимся создавать узлы-элементы ( createElement ) и текстовые узлы ( createTextNode ). А также рассмотрим методы, предназначенные для добавления узлов к дереву ( appendChild , insertBefore ) и для удаления узлов из дерева ( removeChild ).
Добавление узлов к дереву
Добавление нового узла к дереву обычно осуществляется в 2 этапа:
- Создать необходимый узел, используя один из следующих методов:
- createElement() — создаёт элемент (узел) с указанным именем (тегом). Метод createElement(element) имеет один обязательный параметр ( element ) — это строка, содержащая имя создаваемого элемент (тега). Указывать имя элемента (тега) в параметре необходимо заглавными буквами. В качестве результата данный метод возвращает элемент, который был создан.
- createTextNode() — создаёт текстовый узел с указанным текстом. Метод createTextNode(text) имеет один обязательный параметр ( text ) — это строка, содержащая текст текстового узла. В качестве результата данный метод возвращает текстовый узел, который был создан.
- Указать место в дереве, куда необходимо вставить узел. Для этого необходимо воспользоваться одним из следующих методов:
- appendChild() — добавляет узел как последний дочерний узел элемента, для которого вызывается данный метод. Метод appendChild(node) имеет один обязательный параметр это узел ( node ), который Вы хотите добавить. В качестве результата данный метод возвращает добавленный узел.
- insertBefore() — вставляет узел как дочерний узел элемента, для которого вызывается данный метод. Метод insertBefore(newNode,existingNode) имеет два параметра: newNode (обязательный) — узел, который Вы хотите добавить, existingNode (не обязательный) — это дочерний узел элемента перед которым, необходимо вставить узел. Если второй параметр ( existingNode ) не указать, то данный метод вставит его в конец, т.е. в качестве последнего дочернего узла элемента для которого вызывается данный метод. В качестве результата метод insertBefore() возвращает вставленный узел.
Рассмотрим более сложный пример, в котором добавим к дереву узел LI , содержащий текстовый узел с текстом "Смартфон", в конец списка ul .
Для этого необходимо выполнить следующее:
- Создать элемент (узел) LI .
- Создать текстовый узел, содержащий текст "Смартфон".
- Добавить созданный текстовый узел как последний дочерний узел только что созданному элементу LI
- Добавить недавно созданный узел LI как последний дочерний узел элемента ul
Методы appendChild() и insertBefore() при работе с существующими узлами
Работа с существующими узлами методами appendChild() и insertBefore() также осуществляется в 2 этапа:
- Получить существующий узел в дереве.
- Указать место, куда необходимо вставить узел, с помощью метода appendChild() или insertBefore() . При этом узел будет удалён из предыдущего места.
Например, добавить существующий элемент li , содержащий текст “Планшет" в начало списка (при этом он будет удалён из предыдущего места):
Задание
- Имеется два списка в документе. Необходимо переместить элементы из второго списка в первый.
- Создать список, текстовое поле и 2 кнопки. Написать код на языке JavaScript, который в зависимости от нажатой кнопки добавляет текст, находящийся в текстовом поле, в начало или в конец списка.
Удаление узлов
Удаление узла из дерева осуществляется в 2 этапа:
- Получить (найти) этот узел в дереве. Это действие обычно осуществляется одним из следующих методов: getElementById() , getElementsByClassName() , getElementsByTagName() , getElementsByName() , querySelector() или querySelectorAll() .
- Вызвать у родительского узла метод removeChild() , которому в качестве параметра необходимо передать узел, который мы хотим у него удалить.
Метод removeChild() возвращает в качестве значения удалённый узел или null , если узел, который мы хотели удалить, не существовал.
Например, удалить последний дочерний элемент у элемента, имеющего :
Например, удалить все дочерние узлы у элемента, имеющего :
Модификации DOM – это ключ к созданию «живых» страниц.
Здесь мы увидим, как создавать новые элементы «на лету» и изменять уже существующие.
Пример: показать сообщение
Рассмотрим методы на примере – а именно, добавим на страницу сообщение, которое будет выглядеть получше, чем alert .
Это был пример HTML. Теперь давайте создадим такой же div , используя JavaScript (предполагаем, что стили в HTML или во внешнем CSS-файле).
Создание элемента
DOM-узел можно создать двумя методами:
Создаёт новый элемент с заданным тегом:
Создаёт новый текстовый узел с заданным текстом:
Создание сообщения
В нашем случае сообщение – это div с классом alert и HTML в нём:
Мы создали элемент, но пока он только в переменной. Мы не можем видеть его на странице, поскольку он не является частью документа.
Методы вставки
Чтобы наш div появился, нам нужно вставить его где-нибудь в document . Например, в document.body .
Для этого есть метод append , в нашем случае: document.body.append(div) .
Вот полный пример:
Вот методы для различных вариантов вставки:
- node.append(. nodes or strings) – добавляет узлы или строки в конец node ,
- node.prepend(. nodes or strings) – вставляет узлы или строки в начало node ,
- node.before(. nodes or strings) –- вставляет узлы или строки до node ,
- node.after(. nodes or strings) –- вставляет узлы или строки после node ,
- node.replaceWith(. nodes or strings) –- заменяет node заданными узлами или строками.
Вот пример использования этих методов, чтобы добавить новые элементы в список и текст до/после него:
Наглядная иллюстрация того, куда эти методы вставляют:
Итоговый список будет таким:
Эти методы могут вставлять несколько узлов и текстовых фрагментов за один вызов.
Например, здесь вставляется строка и элемент:
Весь текст вставляется как текст.
Поэтому финальный HTML будет:
Другими словами, строки вставляются безопасным способом, как делает это elem.textContent .
Поэтому эти методы могут использоваться только для вставки DOM-узлов или текстовых фрагментов.
А что, если мы хотим вставить HTML именно «как html», со всеми тегами и прочим, как делает это elem.innerHTML ?
insertAdjacentHTML/Text/Element
С этим может помочь другой, довольно универсальный метод: elem.insertAdjacentHTML(where, html) .
Первый параметр – это специальное слово, указывающее, куда по отношению к elem производить вставку. Значение должно быть одним из следующих:
- "beforebegin" – вставить html непосредственно перед elem ,
- "afterbegin" – вставить html в начало elem ,
- "beforeend" – вставить html в конец elem ,
- "afterend" – вставить html непосредственно после elem .
Второй параметр – это HTML-строка, которая будет вставлена именно «как HTML».
Так мы можем добавлять произвольный HTML на страницу.
Мы можем легко заметить сходство между этой и предыдущей картинкой. Точки вставки фактически одинаковые, но этот метод вставляет HTML.
У метода есть два брата:
- elem.insertAdjacentText(where, text) – такой же синтаксис, но строка text вставляется «как текст», вместо HTML,
- elem.insertAdjacentElement(where, elem) – такой же синтаксис, но вставляет элемент elem .
Они существуют, в основном, чтобы унифицировать синтаксис. На практике часто используется только insertAdjacentHTML . Потому что для элементов и текста у нас есть методы append/prepend/before/after – их быстрее написать, и они могут вставлять как узлы, так и текст.
Так что, вот альтернативный вариант показа сообщения:
Удаление узлов
Для удаления узла есть методы node.remove() .
Например, сделаем так, чтобы наше сообщение удалялось через секунду:
Если нам нужно переместить элемент в другое место – нет необходимости удалять его со старого.
Все методы вставки автоматически удаляют узлы со старых мест.
Например, давайте поменяем местами элементы:
Клонирование узлов: cloneNode
Как вставить ещё одно подобное сообщение?
Мы могли бы создать функцию и поместить код туда. Альтернатива – клонировать существующий div и изменить текст внутри него (при необходимости).
Иногда, когда у нас есть большой элемент, это может быть быстрее и проще.
- Вызов elem.cloneNode(true) создаёт «глубокий» клон элемента – со всеми атрибутами и дочерними элементами. Если мы вызовем elem.cloneNode(false) , тогда клон будет без дочерних элементов.
Пример копирования сообщения:
DocumentFragment
DocumentFragment является специальным DOM-узлом, который служит обёрткой для передачи списков узлов.
Мы можем добавить к нему другие узлы, но когда мы вставляем его куда-то, он «исчезает», вместо него вставляется его содержимое.
Например, getListContent ниже генерирует фрагмент с элементами
, которые позже вставляются в
-
:
Обратите внимание, что на последней строке с (*) мы добавляем DocumentFragment , но он «исчезает», поэтому структура будет:
DocumentFragment редко используется. Зачем добавлять элементы в специальный вид узла, если вместо этого мы можем вернуть массив узлов? Переписанный пример:
Мы упоминаем DocumentFragment в основном потому, что он используется в некоторых других областях, например, для элемента template, который мы рассмотрим гораздо позже.
Устаревшие методы вставки/удаления
Есть несколько других, более старых, методов вставки и удаления, которые существуют по историческим причинам.
Сейчас уже нет причин их использовать, так как современные методы append , prepend , before , after , remove , replaceWith более гибкие и удобные.
Мы упоминаем о них только потому, что их можно найти во многих старых скриптах:
Добавляет node в конец дочерних элементов parentElem .
Следующий пример добавляет новый
в конец
-
:
Вставляет node перед nextSibling в parentElem .
Следующий пример вставляет новый элемент перед вторым
:
Чтобы вставить newLi в начало, мы можем сделать вот так:
Заменяет oldChild на node среди дочерних элементов parentElem .
Удаляет node из parentElem (предполагается, что он родитель node ).
Этот пример удалит первый
из
-
:
Все эти методы возвращают вставленный/удалённый узел. Другими словами, parentElem.appendChild(node) вернёт node . Но обычно возвращаемое значение не используют, просто вызывают метод.
Несколько слов о «document.write»
Есть ещё один, очень древний метод добавления содержимого на веб-страницу: document.write .
Вызов document.write(html) записывает html на страницу «прямо здесь и сейчас». Строка html может быть динамически сгенерирована, поэтому метод достаточно гибкий. Мы можем использовать JavaScript, чтобы создать полноценную веб-страницу и записать её в документ.
Этот метод пришёл к нам со времён, когда ещё не было ни DOM, ни стандартов… Действительно старые времена. Он всё ещё живёт, потому что есть скрипты, которые используют его.
В современных скриптах он редко встречается из-за следующего важного ограничения:
Вызов document.write работает только во время загрузки страницы.
Если вызвать его позже, то существующее содержимое документа затрётся.
Так что после того, как страница загружена, он уже непригоден к использованию, в отличие от других методов DOM, которые мы рассмотрели выше.
Это его недостаток.
Есть и преимущество. Технически, когда document.write запускается во время чтения HTML браузером, и что-то пишет в документ, то браузер воспринимает это так, как будто это изначально было частью загруженного HTML-документа.
Поэтому он работает невероятно быстро, ведь при этом нет модификации DOM. Метод пишет прямо в текст страницы, пока DOM ещё в процессе создания.
Так что, если нам нужно динамически добавить много текста в HTML, и мы находимся на стадии загрузки, и для нас очень важна скорость, это может помочь. Но на практике эти требования редко сочетаются. И обычно мы можем увидеть этот метод в скриптах просто потому, что они старые.
Итого
Методы для создания узлов:
- document.createElement(tag) – создаёт элемент с заданным тегом,
- document.createTextNode(value) – создаёт текстовый узел (редко используется),
- elem.cloneNode(deep) – клонирует элемент, если deep==true , то со всеми дочерними элементами.
Вставка и удаление:
- node.append(. nodes or strings) – вставляет в node в конец,
- node.prepend(. nodes or strings) – вставляет в node в начало,
- node.before(. nodes or strings) –- вставляет прямо перед node ,
- node.after(. nodes or strings) –- вставляет сразу после node ,
- node.replaceWith(. nodes or strings) –- заменяет node .
- node.remove() –- удаляет node .
- parent.appendChild(node)
- parent.insertBefore(node, nextSibling)
- parent.removeChild(node)
- parent.replaceChild(newElem, node)
Все эти методы возвращают node .
Если нужно вставить фрагмент HTML, то elem.insertAdjacentHTML(where, html) вставляет в зависимости от where :
- "beforebegin" – вставляет html прямо перед elem ,
- "afterbegin" – вставляет html в elem в начало,
- "beforeend" – вставляет html в elem в конец,
- "afterend" – вставляет html сразу после elem .
Также существуют похожие методы elem.insertAdjacentText и elem.insertAdjacentElement , они вставляют текстовые строки и элементы, но они редко используются.
Чтобы добавить HTML на страницу до завершения её загрузки:
После загрузки страницы такой вызов затирает документ. В основном встречается в старых скриптах.
Задачи
createTextNode vs innerHTML vs textContent
У нас есть пустой DOM-элемент elem и строка text .
Какие из этих 3-х команд работают одинаково?
- elem.append(document.createTextNode(text))
- elem.innerHTML = text
- elem.textContent = text
решение
Ответ: 1 и 3.
Результатом обеих команд будет добавление text «как текст» в elem .