Одной из самых уникальных и полезных возможностей JavaScript является его способность манипулировать DOM. Но что такое DOM и как мы можем изменить его? Перейдем сразу к делу...
Что такое DOM веб-страницы?
- Объектная Модель Документа (Document Object Model) - это древовидное представление содержимого веб-страницы или документа.
В чем разница между узлом и элементом?
- Узлом (node) является любой объект в иерархии DOM, в то время как элемент (element) - один специфичный тип из множества узлов.
- Узлы включают в себя элементы, текст внутри элемента, блоки комментариев к коду, невидимые для пользователя, сам документ и даже абстрактные типы, такие как "фрагменты".
Как выбрать определенный узел с помощью селекторов?
- Есть несколько вариантов, как выбрать узел с помощью селекторов CSS.
- Например,
можно выбрать с помощью следующих:
div
div.display
.display
- Существуют также реляционные селекторы, такие как
firstElementChild
илиlastElementChild
. - По итогу вы можете выбрать определенный узел с помощью JavaScript следующим способом:
document.querySelector(".display");
выберет приведенный выше div.
Какие основные методы поиска / добавления / удаления и изменения DOM-узлов?
- Как упоминалось выше, вы можете найти узлы в DOM, используя запросы по селекторам (Query Selectors).
- Чтобы создать элемент, используйте
document.createElement(tagName[, options])
- т.е.
const div = document.createElement('div');
создаст новый элемент div. Однако он еще не добавлен на страницу.
- т.е.
- Чтобы добавить его, используйте следующий код
parentNode.appendChild(childNode)
- т.e.
parentNode.appendChild(div);
- т.e.
- Давайте теперь его удалим. Это можно сделать с помощью команды
parentNode.removeChild(child)
- Данная команда удалит child из parentNode из DOM и вернет ссылку на child.
- т.e.
parentNode.removeChild(div);
- Если у вас есть ссылка на элемент, вы можете ее использовать следующим способом (в данном случае `div` - ссылка на элемент):
div.style.color = 'blue';
- добавляет указанный стильdiv.setAttribute('id', 'theDiv');
- задает значениеtheDiv
атрибуту id нашего узла div.
В чем разница между NodeList и массивом с узлами?
- В том, что NodeList выглядит как массив, но в нем отсутствует некоторые методов, которые есть у массива.
- Решением данной проблемы является использование оператора Array.from() для преобразования NodeList в массив.
Как работают события (или events) и обработчики событий (или listeners)? Как использовать их в вашем коде?
- События - это инструмент, с помощью которого вы делаете страничку динамичной. Они запускаются "обработчиками" и могут срабатывать при загрузке страницы, при щелчке мышью, при нажатии клавиш на клавиатуре и многих, многих других.
- Три основных варианта использования событий:
- Добавить JavaScript-код к атрибуту события элемента HTML.
<button onclick="alert(this.tagName)">Нажми на меня</button>
- Установить свойство "onevent" для объекта DOM в JavaScript-коде.
- Добавить обработчик событий к узлам вашего JavaScript-кода.
// html
<button id="btn">Нажми на меня</button>
// JavaScript
const btn = document.querySelector(‘#btn’);
btn.onclick = (e) => alert(e.target.tagName);
// html
<button id="btn">Нажми и на меня</button>
// JavaScript
var btn = document.querySelector('#btn');
btn.addEventListener(‘click’, (e) => {
alert(e.target.tagName);
});
Как работает "всплытие"?
- Пузырьки - это форма "продвижения событий".
- Это эффективный способ запуска события на нескольких элементах, начиная с внутреннего элемента, и последовательного "всплывания" до внешних элементов.
DOM (или объектная модель документа) - древовидное представление содержимого веб-страницы. DOM - дерево "узлов" с разными отношениями друг относительно друга в зависимости от расположения в документе HTML.
<div id="container">
<div class="display"></div>
<div class="controls"></div>
</div>
В приведенном выше примере <div class ="display"></div>
является "потомком" <div id="container"></div>
и родственным элементом для <div class ="controls"></div>
. По сути структура аналогична фамильному дереву. <div id="container"></div>
является родительским элементом, а два других блока - его дочерними элементами на нижнем уровне. Каждый дочерний элемент лежит в своей собственной "ветви".
При работе с DOM используются "селекторы" для выбора необходимых узлов. Можно также комбинировать селекторы CSS и реляционные свойства. Начнем с селекторов CSS. В приведенном выше примере вы можете получить ссылку на
, используя следующие селекторы:- div.display
- .display
- #container > .display
- div#container > div.display
Вы также можете использовать реляционные селекторы (firstElementChild
или lastElementChild
и др.) со специальными свойствами узла.
const container = document.querySelector("#container");
// выбираем #container div (не парьтесь о синтаксисе, мы его еще обсудим)
console.dir(container.firstElementChild);
// выбираем первого потомка #container => .display
const controls = document.querySelector(".controls");
// выбираем .controls
console.dir(controls.previousElementSibling);
// выбираем предыдущий родственный элемент => .display
Таким образом, вы выбираем определенный узел на основе его отношений с другими узлами вокруг.
Когда ваш HTML-код анализируется веб-браузером, он преобразуется в DOM, как упоминалось выше. Важным моментом является то, что эти узлы являются объектами, к которым привязаны многие свойства и методы. Эти свойства и методы являются основными инструментами, которыми мы будем пользоваться для управления веб-страницей с помощью JavaScript. Мы начнем с запросов по селекторам (Query Selectors), которые помогут выбрать конкретные узлы.
- element.querySelector(selector) возвращает ссылку на первый элемент, соответствующий selector.
- element.querySelectorAll(selectors) возвращает Node List (список узлов), содержащий ссылки на все элементы, соответствующие selectors.
Есть несколько более конкретных запросов, которые имеют (незначительные) преимущества в производительности, но мы не будем их рассматривать сейчас
Важно отметить, что querySelectorAll возвращает не массив. Он выглядит как массив и в некоторой степени ведет себя как массив, но на самом деле это Node List (список узлов). В массиве присутствуют методы, которые отсутствуют в Node List. Но, к счастью, если возникают проблемы, можно воспользоваться преобразованием Node List в массив с помощью Array.from() или spread оператора
- document.createElement(tagName[, options]) создает новый элемент типа, заданного с помощью tagName.
[options]
- необязательные параметры функции. Не беспокойтесь о них на данный момент.
const div = document.createElement('div');
Эта функция НЕ помещает ваш новый элемент в DOM - она просто создает его в памяти. Это сделано для того, чтобы вы могли манипулировать элементом (добавляя стили, классы, идентификаторы, текст и т. д.) перед размещением его на странице. Вы можете поместить элемент в DOM одним из следующих способов.
- parentNode.appendChild(childNode) добавляет childNode в конец элемента parentNode.
- parentNode.insertBefore(newNode, referenceNode) вставляет newNode в parentNode перед referenceNode.
- parentNode.removeChild(childNode) удаляет childNode из parentNode в DOM и возвращает ссылку на childNode.
Если у вас есть ссылка на элемент, вы можете использовать эту ссылку для изменения свойств элемента. Это позволит сделать много полезных изменений, таких как добавление / удаление и изменение атрибутов, изменение классов, добавление inline-стилей и многое другое.
const div = document.createElement('div');
// создаем новый div, на который ссылается переменная 'div'
div.style.color = 'blue';
// добавляет указанный стиль
div.style.cssText = 'color: blue; background: white';
// добавляет несколько стилей
div.setAttribute ('style', 'color: blue; background: white');
// добавляет несколько стилей
Больше информации о inline-стилях.
Обычно стили в JavaScript аналогичны стилям в CSS, за исключением того, что стили, написанные через дефис, изменены на camelCase. Например, background-color
становится backgroundColor
.
div.setAttribute('id', 'theDiv');
// если id уже задан, обновить его до 'theDiv'
// иначе создать атрибут id со значением "theDiv"
div.getAttribute('id');
// возвращает значение указанного атрибута, в
// данном случае "theDiv"
div.removeAttribute('id');
// удаляет указанный атрибут
Больше информации о доступных атрибутах в разделе MDN "Атрибуты HTML".
div.classList.add('new');
// добавляет класс "new" в ваш созданный div
div.classList.remove('new');
// удаляем класс "new" из div
div.classList.toggle('active');
// если у div нет класса "active", добавляем его
// если у div есть класс "active", удаляем его
// по сути toggle - переключатель
Более правильно (и более чисто) переключать класс с помощью toggle, чем добавлять и удалять его.
div.textContent = 'Привет, мир!'
// создаем текстовый узел, содержащий "Привет, мир!" а также
// вставляем его в div
div.innerHTML = '<span> Hello World! </span>';
// вставляем HTML в div
Обратите внимание, что textContent предпочтительнее для текста, а innerHTML следует использовать с осторожностью, поскольку при неправильном использовании он может создать угрозу безопасности ваших пользователей.
Давайте уделим минуту тому, что изучили, и закрепим все практикой. Рассмотрите пример создания и добавления элемента DOM на веб-страницу.
// наш html:
<body>
<h1>
Заголовок нашей страницы
</h1>
<div id="container"></div>
</body>
// наш javascript-файл
const container = document.querySelector("#container");
const content = document.createElement("div");
content.classList.add("content");
content.textContent = "Это супер крутой текст!";
container.appendChild(content);
В файле JavaScript сначала мы формируем ссылку на div с классом container
, который уже присутствует в нашем HTML (с помощью querySelector
). Затем мы создаем новый div и сохраняем его в переменной content
. Добавляем класс и текст в div content
и, наконец, внедряем этот div в container
(с помощью метода appendChild
). В целом, все просто. После запуска данного кода наше дерево DOM будет выглядеть так:
// The DOM
<body>
<h1>
Заголовок нашей страницы
</h1>
<div id="container">
<div class="content">
Это супер крутой текст!
</div>
</div>
</body>
Помните, что JavaScript изменяет не ваш HTML-файл, а DOM. Ваш HTML-файл будет выглядеть также, но JavaScript изменит способ отрисовки.
Важное замечание: Ваш JavaScript, в большинстве случаев, запускается всякий раз, когда запускается файл JS или когда тег script встречается в HTML. Если вы включаете JavaScript в начало вашего файла, многие из этих методов манипулирования DOM не будут работать, потому что код JS выполняется до создания узлов в DOM. Самый простой способ исправить это - включить JavaScript в конец HTML-файла, чтобы он запускался после анализа и создания DOM-узлов.
Скопируйте приведенный выше пример на ваш компьютер. Чтобы все заработало, вам необходимо добавить недостающие части HTML-скелета и добавить ссылку на ваш javascript-файл (можно еще поместить javascript в тег script
на странице). Убедитесь, что все работает корректно!
После добавьте следующие элементы в контейнер (элемент div с атрибутом id равном container
), используя ТОЛЬКО javascript - не добавляйте HTML или CSS-код.
<p>
с красным текстом "Эй, я красный!";<h3>
с синим текстом: "Я синий h3!";<div>
с черной рамкой и розовым цветом фона и следующими элементами внутри:- другой
<h1>
, который говорит "А я в div!", <p>
с надписью "Я ТОЖЕ!",- Подсказка: после создания div с помощью createElement добавьте к нему
<h1>
и<p>
перед внедрением его вcontainer
.
- другой
Теперь, когда мы понимаем, как манипулировать DOM с помощью JavaScript, следующим шагом будет изучение того, как сделать это динамически или по требованию! События - это то, с помощью чего мы творим волшебство на страницах. События - это действия, которые происходят на вашей веб-странице, такие, как щелчок мышью или нажатие клавиши. Используя JavaScript, мы можем заставить нашу веб-страницу обрабатывать эти события и реагировать на них.
Есть три основных способа сделать это:
- мы можем вставить функцию в атрибут HTML-элемента,
- мы можем установить свойство
onevent
для объекта DOM в JavaScript - мы можем присоединить обработчики событий к узлам в JavaScript.
Обработчики событий, безусловно, являются предпочтительным методом, но вы будете регулярно видеть другие варианты, поэтому мы рассмотрим все.
Мы собираемся создать 3 кнопки, которые выводят окно с текстом "КНОПКА" при нажатии. Запустите их, используя собственный HTML-файл или CodePen.
<button onclick="alert('Привет, Мир!')">Нажми на меня</button>
Это решение не идеально, потому что мы нагромождаем наш HTML JavaScript-кодом. Кроме того, может быть только одно событие onclick
на элемент.
// файл HTML
<button id="btn">Нажмите на меня</button>
// файл JavaScript
const btn = document.querySelector('#btn');
btn.onclick = () => alert('Привет, Мир!');
(незнакомы со стрелочными функциями? Гляньте сюда.)
Уже лучше. Мы переместили код из HTML в JavaScript-файл, но все еще есть проблема: элемент DOM может иметь только одно свойство onclick
.
// файл HTML
<button id="btn">Нажми и на меня</button>
// файл JavaScript
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
alert ("Привет, мир");
});
Теперь мы решили не только проблему с разделением кода, но и с множеством обработчиков событий. Метод 3 более гибкий и мощный, хотя его немного сложнее реализовать.
Обратите внимание, что все 3 метода могут использоваться с именованными функциями:
// файл HTML
// МЕТОД 1
<button onclick="alertFunction()">НАЖМИ МЕНЯ :0</button>
function alertFunction() {
alert("О ДА! ТЫ ЭТО СДЕЛАЛ!");
}
// МЕТОД 2
btn.onclick = alertFunction;
// МЕТОД 3
btn.addEventListener('click', alertFunction);
Использование именованных функций может упростить ваш код. Особенно это хорошая идея, если вы хотите использовать функцию в разных местах.
Используя все три метода, мы можем получить дополнительную информацию о событии, передав параметр в функцию. Попробуйте запустить:
btn.addEventListener('click', function (e) {
console.log(е);
})
Параметр e
функции является объектом, который ссылается на само событие. Внутри этого объекта есть доступ ко многим полезным свойствам и функциям:
- какая кнопка мыши была нажата,
- какая клавиша была нажата,
- получить информацию о target - узле DOM, по которому щелкнули.
Попробуйте:
btn.addEventListener('click', function (e) {
console.log(e.target);
})
а теперь это:
btn.addEventListener('click', function (e) {
e.target.style.background = 'blue';
})
круто, а?
Если вы прикрепляете много схожих обработчиков событий ко многим элементам, то получите огромное количество одинакового кода.
Есть несколько способов упростить себе жизнь. Выше мы узнали, что можем получить NodeList всех элементов, соответствующих конкретному селектору, с помощью querySelectorAll('selector')
. Чтобы добавить обработчик к каждому элементу, нам нужно перебрать весь список следующим образом:
<div id="container">
<button id="1">Нажмите меня</button>
<button id="2">Нажмите меня</button>
<button id="3">Нажмите меня</button>
</ DIV>
// кнопки - это NodeList (список узлов). NodeList похож на массив.
const buttons = document.querySelectorAll('button');
// мы используем метод .forEach для итерации (проходу) по каждой кнопке
buttons.forEach((button) => {
// и для каждой кнопки мы добавляем слушателя 'click'
button.addEventListener('click', (e) => {
alert(button.id);
});
});
Это лишь вершина айсберга, когда дело доходит до манипулирования DOM и обработки событий, но этого достаточно, чтобы вы смогли решать задачи. До сих пор мы использовали исключительно событие click
, но доступно еще множество других.
Полезные события:
- click,
- dblclick,
- keypress,
- keydown,
- keyup.
Вы можете найти более полный список с объяснениями на этой странице или тут на русском.
Управление веб-страницами является основным преимуществом языка JavaScript! Эти методы - вещи, с которыми вы, вероятно, будете связываться с каждый день как разработчик интерфейса, поэтому давайте потренируемся!
-
Выполните первое упражнение JavaScript30 от Wes Bos, склонировав репозиторий по адресу https://github.com/wesbos/JavaScript30. Гляньте видеоурок для получения инструкций по проекту.
-
Вернитесь к игре "камень, ножницы, бумага" из предыдущего урока и создайте простой пользовательский интерфейс, чтобы игрок мог играть, нажимая кнопки, а не вводя свой ответ в командной строке.
- Скопируйте старый код в новый файл, чтобы не потерять.
- Пока удалите логику, которая запускает пять раундов.
- Создайте три кнопки, по одной на каждый выбор (камень, ножницы, бумага). Добавьте обработчик событий, вызывающий функцию
playRound
с нужным значениемplayerSelection
каждый раз, когда нажимается кнопка. (вы можете использоватьconsole.log
для этого шага) - Добавьте
div
для отображения результатов и измените все вашиconsole.log
на DOM-методы. - Покажите текущий счет и объявите победителя игры, как только один игрок наберет 5 очков.
- Вам, вероятно, придется переработать / переписать свой исходный код, чтобы все заработало. Это нормально! Переработка старого кода - важная часть жизни программиста.
- eng DOM Enlightenment
- eng JavaScript30
- eng An introduction to DOM
- eng Если вы готовы начать знакомство с JQuery, то вам поможет этот сайт
- eng Этот урок на W3Schools просто и понятно рассказывает о DOM
- Eloquent JS - DOM
- Eloquent JS - Handling Events
- Создание модели DOM от Google Developers
- Дерево DOM
- Что такое Объектная Модель Документа (DOM)? от MDN