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 этапа:

  1. Создать необходимый узел, используя один из следующих методов:
    • createElement() — создаёт элемент (узел) с указанным именем (тегом). Метод createElement(element) имеет один обязательный параметр ( element ) — это строка, содержащая имя создаваемого элемент (тега). Указывать имя элемента (тега) в параметре необходимо заглавными буквами. В качестве результата данный метод возвращает элемент, который был создан.
    • createTextNode() — создаёт текстовый узел с указанным текстом. Метод createTextNode(text) имеет один обязательный параметр ( text ) — это строка, содержащая текст текстового узла. В качестве результата данный метод возвращает текстовый узел, который был создан.
    • Указать место в дереве, куда необходимо вставить узел. Для этого необходимо воспользоваться одним из следующих методов:
      • appendChild() — добавляет узел как последний дочерний узел элемента, для которого вызывается данный метод. Метод appendChild(node) имеет один обязательный параметр это узел ( node ), который Вы хотите добавить. В качестве результата данный метод возвращает добавленный узел.
      • insertBefore() — вставляет узел как дочерний узел элемента, для которого вызывается данный метод. Метод insertBefore(newNode,existingNode) имеет два параметра: newNode (обязательный) — узел, который Вы хотите добавить, existingNode (не обязательный) — это дочерний узел элемента перед которым, необходимо вставить узел. Если второй параметр ( existingNode ) не указать, то данный метод вставит его в конец, т.е. в качестве последнего дочернего узла элемента для которого вызывается данный метод. В качестве результата метод insertBefore() возвращает вставленный узел.

      Рассмотрим более сложный пример, в котором добавим к дереву узел LI , содержащий текстовый узел с текстом "Смартфон", в конец списка ul .

      Для этого необходимо выполнить следующее:

      1. Создать элемент (узел) LI .
      2. Создать текстовый узел, содержащий текст "Смартфон".
      3. Добавить созданный текстовый узел как последний дочерний узел только что созданному элементу LI
      4. Добавить недавно созданный узел LI как последний дочерний узел элемента ul

      Методы appendChild() и insertBefore() при работе с существующими узлами

      Работа с существующими узлами методами appendChild() и insertBefore() также осуществляется в 2 этапа:

      1. Получить существующий узел в дереве.
      2. Указать место, куда необходимо вставить узел, с помощью метода appendChild() или insertBefore() . При этом узел будет удалён из предыдущего места.

      Например, добавить существующий элемент li , содержащий текст “Планшет" в начало списка (при этом он будет удалён из предыдущего места):

      Задание

      • Имеется два списка в документе. Необходимо переместить элементы из второго списка в первый.
      • Создать список, текстовое поле и 2 кнопки. Написать код на языке JavaScript, который в зависимости от нажатой кнопки добавляет текст, находящийся в текстовом поле, в начало или в конец списка.

      Удаление узлов

      Удаление узла из дерева осуществляется в 2 этапа:

      1. Получить (найти) этот узел в дереве. Это действие обычно осуществляется одним из следующих методов: getElementById() , getElementsByClassName() , getElementsByTagName() , getElementsByName() , querySelector() или querySelectorAll() .
      2. Вызвать у родительского узла метод 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-х команд работают одинаково?

      1. elem.append(document.createTextNode(text))
      2. elem.innerHTML = text
      3. elem.textContent = text

      решение

      Ответ: 1 и 3.

      Результатом обеих команд будет добавление text «как текст» в elem .


      [an error occurred while processing the directive]
      Карта сайта