Skip to content

Latest commit

 

History

History
495 lines (374 loc) · 31.7 KB

00037.md

File metadata and controls

495 lines (374 loc) · 31.7 KB

Манипуляции с DOM

Введение

Одной из самых уникальных и полезных возможностей 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);
  • Давайте теперь его удалим. Это можно сделать с помощью команды 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)? Как использовать их в вашем коде?
  • События - это инструмент, с помощью которого вы делаете страничку динамичной. Они запускаются "обработчиками" и могут срабатывать при загрузке страницы, при щелчке мышью, при нажатии клавиш на клавиатуре и многих, многих других.
  • Три основных варианта использования событий:
    1. Добавить JavaScript-код к атрибуту события элемента HTML.
      • <button onclick="alert(this.tagName)">Нажми на меня</button>
    2. Установить свойство "onevent" для объекта DOM в JavaScript-коде.
      • // html
        <button id="btn">Нажми на меня</button>

        // JavaScript
        const btn = document.querySelector(‘#btn’);
        btn.onclick = (e) => alert(e.target.tagName);
    3. Добавить обработчик событий к узлам вашего JavaScript-кода.
      • // html
        <button id="btn">Нажми и на меня</button>

        // JavaScript
        var btn = document.querySelector('#btn');
        btn.addEventListener(‘click’, (e) => {
          alert(e.target.tagName);
        });
Как работает "всплытие"?
  • Пузырьки - это форма "продвижения событий".
  • Это эффективный способ запуска события на нескольких элементах, начиная с внутреннего элемента, и последовательного "всплывания" до внешних элементов.

DOM - объектная модель документа

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

Таким образом, вы выбираем определенный узел на основе его отношений с другими узлами вокруг.

Методы DOM

Когда ваш HTML-код анализируется веб-браузером, он преобразуется в DOM, как упоминалось выше. Важным моментом является то, что эти узлы являются объектами, к которым привязаны многие свойства и методы. Эти свойства и методы являются основными инструментами, которыми мы будем пользоваться для управления веб-страницей с помощью JavaScript. Мы начнем с запросов по селекторам (Query Selectors), которые помогут выбрать конкретные узлы.

Запросы по селекторам (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'

Добавление inline-стилей

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

Добавим HTML

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.

метод 1

<button onclick="alert('Привет, Мир!')">Нажми на меня</button>

Это решение не идеально, потому что мы нагромождаем наш HTML JavaScript-кодом. Кроме того, может быть только одно событие onclick на элемент.

метод 2

// файл HTML
<button id="btn">Нажмите на меня</button>
// файл JavaScript
const btn = document.querySelector('#btn');
btn.onclick = () => alert('Привет, Мир!');

(незнакомы со стрелочными функциями? Гляньте сюда.)

Уже лучше. Мы переместили код из HTML в JavaScript-файл, но все еще есть проблема: элемент DOM может иметь только одно свойство onclick.

метод 3

// файл 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! Эти методы - вещи, с которыми вы, вероятно, будете связываться с каждый день как разработчик интерфейса, поэтому давайте потренируемся!

  1. Выполните первое упражнение JavaScript30 от Wes Bos, склонировав репозиторий по адресу https://github.com/wesbos/JavaScript30. Гляньте видеоурок для получения инструкций по проекту.

  2. Вернитесь к игре "камень, ножницы, бумага" из предыдущего урока и создайте простой пользовательский интерфейс, чтобы игрок мог играть, нажимая кнопки, а не вводя свой ответ в командной строке.

    • Скопируйте старый код в новый файл, чтобы не потерять.
    • Пока удалите логику, которая запускает пять раундов.
    • Создайте три кнопки, по одной на каждый выбор (камень, ножницы, бумага). Добавьте обработчик событий, вызывающий функцию playRound с нужным значением playerSelection каждый раз, когда нажимается кнопка. (вы можете использовать console.log для этого шага)
    • Добавьте div для отображения результатов и измените все ваши console.log на DOM-методы.
    • Покажите текущий счет и объявите победителя игры, как только один игрок наберет 5 очков.
    • Вам, вероятно, придется переработать / переписать свой исходный код, чтобы все заработало. Это нормально! Переработка старого кода - важная часть жизни программиста.

Дополнительные ресурсы