Руководство по dom

Время на прочтение
21 мин

Количество просмотров 109K

Содержание

  • Введение
  • Величины, типы и операторы
  • Структура программ
  • Функции
  • Структуры данных: объекты и массивы
  • Функции высшего порядка
  • Тайная жизнь объектов
  • Проект: электронная жизнь
  • Поиск и обработка ошибок
  • Регулярные выражения
  • Модули
  • Проект: язык программирования
  • JavaScript и браузер
  • Document Object Model
  • Обработка событий
  • Проект: игра-платформер
  • Рисование на холсте
  • HTTP
  • Формы и поля форм
  • Проект: Paint
  • Node.js
  • Проект: веб-сайт по обмену опытом
  • Песочница для кода

Когда вы открываете веб-страницу в браузере, он получает исходный текст HTML и разбирает (парсит) его примерно так, как наш парсер из главы 11 разбирал программу. Браузер строит модель структуры документа и использует её, чтобы нарисовать страницу на экране.

Это представление документа и есть одна из игрушек, доступных в песочнице JavaScript. Вы можете читать её и изменять. Она изменяется в реальном времени – как только вы её подправляете, страница на экране обновляется, отражая изменения.

Структура документа

Можно представить HTML как набор вложенных коробок. Теги вроде <body> и </body> включают в себя другие теги, которые в свою очередь включают теги, или текст. Вот вам пример документа из предыдущей главы:

<!doctype html>
<html>
  <head>
    <title>Моя домашняя страничка</title>
  </head>
  <body>
    <h1> Моя домашняя страничка </h1>
    <p>Привет, я Марийн и это моя домашняя страничка.</p>
    <p>А ещё я книжку написал! Читайте её
      <a href="http://eloquentjavascript.net">здесь</a>.</p>
  </body>
</html>

У этой страницы следующая структура:

Структура данных, использующаяся браузером для представления документа, отражает его форму. Для каждой коробки есть объект, с которым мы можем взаимодействовать и узнавать про него разные данные – какой тег он представляет, какие коробки и текст содержит. Это представление называется Document Object Model (объектная модель документа), или сокращённо DOM.

Мы можем получить доступ к этим объектам через глобальную переменную document. Её свойство documentElement ссылается на объект, представляющий тег . Он также предоставляет свойства head и body, в которых содержатся объекты для соответствующих элементов.

Деревья

Вспомните синтаксические деревья из главы 11. Их структура удивительно похожа на структуру документа браузера. Каждый узел может ссылаться на другие узлы, потомки, которые, в свою очередь, могут иметь своих собственных потомков. Эта структура – типичный пример вложенных структур, где элементы содержат подэлементы, похожие на них самих.

Мы зовём структуру данных деревом, когда она разветвляется, не имеет циклов (узел не может содержать сам себя явно или неявно), и имеет единственный ярко выраженный «корень». В случае DOM в качестве корня выступает document.documentElement.

Деревья часто встречаются в вычислительной науке. В дополнение к представлению рекурсивных структур вроде документа HTML или программ, они часто используются для работы с сортированными наборами данных, потому что элементы обычно проще найти или вставлять в отсортированное дерево, чем в отсортированный одномерный массив.

У типичного дерева есть разные узлы. У синтаксического дерева языка Egg были переменные, значения и приложения. У приложений всегда были дочерние ветви, а переменные и значения были «листьями», то есть узлами без дочерних ответвлений.

То же и у DOM. Узлы для обычных элементов, представляющих теги HTML, определяют структуру документа. У них могут быть дочерние узлы. Пример такого узла — document.body. Некоторые из этих дочерних узлов могут оказаться листьями – например, текст или комментарии (в HTML комментарии записываются между символами <!-- и -->).

У каждого узлового объекта DOM есть свойство nodeType, содержащее цифровой код, определяющий тип узла. У обычных элементов он равен 1, что также определено в виде свойства-константы document.ELEMENT_NODE. У текстовых узлов, представляющих отрывки текста, он равен 3 (document.TEXT_NODE). У комментариев — 8 (document.COMMENT_NODE).

То есть, вот ещё один способ графически представить дерево документа:

Листья – текстовые узлы, а стрелки показывают взаимоотношения отец-ребёнок между узлами.

Стандарт

Использовать загадочные цифры для представления типа узла – это подход не в стиле JavaScript. Позже мы встретимся с другими частями интерфейса DOM, которые тоже кажутся чуждыми и нескладными. Причина в том, что DOM разрабатывался не только для JavaScript. Он пытается определить интерфейс, не зависящий от языка, который можно использовать и в других системах – не только в HTML, но и в XML, который представляет из себя формат данных общего назначения с синтаксисом, напоминающим HTML.

Получается неудобно. Хотя стандарты – и весьма полезная штука, в нашем случае преимущество независимости от языка не такое уж и полезное. Лучше иметь интерфейс, хорошо приспособленный к языку, который вы используете, чем интерфейс, который будет знаком при использовании разных языков.

Чтобы показать неудобную интеграцию с языком, рассмотрим свойство childNodes, которое есть у узлов DOM. В нём содержится объект, похожий на массив, со свойством length, и пронумерованные свойства для доступа к дочерним узлам. Но это – экземпляр типа NodeList, не настоящий массив, поэтому у него нет методов вроде slice или forEach.

Есть также проблемы, связанные с плохой продуманностью системы. К примеру, нельзя создать новый узел и сразу добавить к нему свойства или дочерние узлы. Сначала нужно его создать, затем добавить дочерние по одному, и в конце назначить свойства по одному, с использованием побочных эффектов. Код, плотно работающий с DOM, получается длинным, некрасивым и со множеством повторов.

Но эти проблемы не фатальные. JavaScript позволяет создавать абстракции. Легко написать вспомогательные функции, позволяющие выражать операции более понятно и коротко. Вообще, такого рода инструменты предоставляют много библиотек, направленных на программирование для браузера.

Обход дерева

Узлы DOM содержат много ссылок на соседние. Это показано на диаграмме:

Хотя тут показано только по одной ссылке каждого типа, у каждого узла есть свойство parentNode, указывающего на его родительский узел. Также у каждого узла-элемента (тип 1) есть свойство childNodes, указывающее на массивоподобный объект, содержащий его дочерние узлы.

В теории можно пройти в любую часть дерева, используя только эти ссылки. Но JavaScript предоставляет нам много дополнительных вспомогательных ссылок. Свойства firstChild и lastChild показывают на первый и последний дочерний элементы, или содержат null у тех узлов, у которых нет дочерних. previousSibling и nextSibling указывают на соседние узлы – узлы того же родителя, что и текущего узла, но находящиеся в списке сразу до или после текущей. У первого узла свойство previousSibling будет null, а у последнего nextSibling будет null.

При работе с такими вложенными структурами пригождаются рекурсивные функции. Следующая ищет в документе текстовые узлы, содержащие заданную строку, и возвращает true, когда находит:

function talksAbout(node, string) {
  if (node.nodeType == document.ELEMENT_NODE) {
    for (var i = 0; i < node.childNodes.length; i++) {
      if (talksAbout(node.childNodes[i], string))
        return true;
    }
    return false;
  } else if (node.nodeType == document.TEXT_NODE) {
    return node.nodeValue.indexOf(string) > -1;
  }
}

console.log(talksAbout(document.body, "книг"));
// → true

Свойства текстового узла nodeValue содержит строчку текста.

Поиск элементов

Часто бывает полезным ориентироваться по этим ссылкам между родителями, детьми и родственными узлами и проходить по всему документу. Однако если нам нужен конкретный узел в документе, очень неудобно идти по нему, начиная с document.body и тупо перебирая жёстко заданный в коде путь. Поступая так, мы вносим в программу допущения о точной структуре документа – а её мы позже можем захотеть поменять. Другой усложняющий фактор – текстовые узлы создаются даже для пробелов между узлами. В документе из примера у тега body не три дочерних (h1 и два p), а целых семь: эти три плюс пробелы до, после и между ними.

Так что если нам нужен атрибут href из ссылки, мы не должны писать в программе что-то вроде: «второй ребёнок шестого ребёнка document.body». Лучше бы, если б мы могли сказать: «первая ссылка в документе». И так можно сделать:

var link = document.body.getElementsByTagName("a")[0];
console.log(link.href);

У всех узлов-элементов есть метод getElementsByTagName, собирающий все элементы с данным тэгом, которые происходят (прямые или не прямые потомки) от этого узла, и возвращает его в виде массивоподобного объекта.

Чтобы найти конкретный узел, можно задать ему атрибут id и использовать метод document.getElementById.

<p>Мой страус Гертруда:</p>
<p><img id="gertrude" src="img/ostrich.png"></p>

<script>
  var ostrich = document.getElementById("gertrude");
  console.log(ostrich.src);
</script>

Третий метод – getElementsByClassName, который, как и getElementsByTagName, ищет в содержимом узла-элемента и возвращает все элементы, содержащие в своём классе заданную строчку.

Меняем документ

Почти всё в структуре DOM можно менять. У узлов-элементов есть набор методов, которые используются для их изменения. Метод removeChild удаляет заданный дочерний узел. Для добавления узла можно использовать appendChild, который добавляет узел в конец списка, либо insertBefore, добавляющий узел, переданную первым аргументом, перед узлом, переданным вторым аргументом.

<p>Один</p>
<p>Два</p>
<p>Три</p>

<script>
  var paragraphs = document.body.getElementsByTagName("p");
  document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>

Узел может существовать в документе только в одном месте. Поэтому вставляя параграф «Три» перед параграфом «Один» мы фактически удаляем его из конца списка и вставляем в начало, и получаем «Три/Один/Два». Все операции по вставке узла приведут к его исчезновению с текущей позиции (если у него таковая была).

Метод replaceChild используется для замены одного дочернего узла другим. Он принимает два узла: новый, и тот, который надо заменить. Заменяемый узел должен быть дочерним узлом того элемента, чей метод мы вызываем. Как replaceChild, так и insertBefore в качестве первого аргумента ожидают получить новый узел.

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

В следующем примере нам надо сделать скрипт, заменяющий все картинки (тег <img>) в документе текстом, содержащимся в их атрибуте “alt”, который задаёт альтернативное текстовое представление картинки.

Для этого надо не только удалить картинки, но и добавить новые текстовые узлы им на замену. Для этого мы используем метод document.createTextNode.

<p>Это <img src="img/cat.png" alt="Кошка"> в
  <img src="img/hat.png" alt="сапожках">.</p>

<p><button onclick="replaceImages()">Заменить</button></p>

<script>
  function replaceImages() {
    var images = document.body.getElementsByTagName("img");
    for (var i = images.length - 1; i >= 0; i--) {
      var image = images[i];
      if (image.alt) {
        var text = document.createTextNode(image.alt);
        image.parentNode.replaceChild(text, image);
      }
    }
  }
</script>

Получая строку, createTextNode даёт нам тип 3 узла DOM (текстовый), который мы можем вставить в документ, чтобы он был показан на экране.

Цикл по картинкам начинается в конце списка узлов. Это сделано потому, что список узлов, возвращаемый методом getElementsByTagName (или свойством childNodes) постоянно обновляется при изменениях документа. Если б мы начали с начала, удаление первой картинки привело бы к потере списком первого элемента, и во время второго прохода цикла, когда i равно 1, он бы остановился, потому что длина списка стала бы также равняться 1.

Если вам нужно работать с фиксированным списком узлов вместо «живого», можно преобразовать его в настоящий массив при помощи метода slice.

var arrayish = {0: "один", 1: "два", length: 2};
var real = Array.prototype.slice.call(arrayish, 0);
real.forEach(function(elt) { console.log(elt); });
// → один
//   два

Для создания узлов-элементов (тип 1) можно использовать document.createElement. Метод принимает имя тега и возвращает новый пустой узел заданного типа. Следующий пример определяет инструмент elt, создающий узел-элемент и использующий остальные аргументы в качестве его детей. Эта функция потом используется для добавления дополнительной информации к цитате.

<blockquote id="quote">
Никакая книга не может быть закончена. Во время работы над ней мы узнаём достаточно для того, чтобы найти её незрелой сразу же после того, как мы отвлеклись от неё.
</blockquote>

<script>
  function elt(type) {
    var node = document.createElement(type);
    for (var i = 1; i < arguments.length; i++) {
      var child = arguments[i];
      if (typeof child == "string")
        child = document.createTextNode(child);
      node.appendChild(child);
    }
    return node;
  }

  document.getElementById("quote").appendChild(
    elt("footer", "—",
        elt("strong", "Карл Поппер"),
        ", предисловие ко второму изданию ",
        elt("em", "Открытое общество и его враги "),
        ", 1950"));
</script>

Атрибуты

К некоторым атрибутам элементов, типа href у ссылок, можно получить доступ через одноимённое свойство объекта. Это возможно для ограниченного числа часто используемых стандартных атрибутов.

Но HTML позволяет назначать узлам любые атрибуты. Это полезно, т.к. позволяет вам хранить дополнительную информацию в документе. Если вы придумаете свои названия атрибутов, их не будет среди свойств узла-элемента. Вместо этого вам надо будет использовать методы getAttribute и setAttribute для работы с ними.

<p data-classified="secret">Код запуска 00000000.</p>
<p data-classified="unclassified">У кошки четыре ноги.</p>

<script>
  var paras = document.body.getElementsByTagName("p");
  Array.prototype.forEach.call(paras, function(para) {
    if (para.getAttribute("data-classified") == "secret")
      para.parentNode.removeChild(para);
  });
</script>

Рекомендую перед именами придуманных атрибутов ставить “data-“, чтобы быть уверенным, что они не конфликтуют с любыми другими. В качестве простого примера мы напишем подсветку синтаксиса, который ищет теги <pre> (“preformatted”, предварительно отформатированный – используется для кода и простого текста) с атрибутом data-language (язык) и довольно грубо пытается подсветить ключевые слова в языке.

function highlightCode(node, keywords) {
  var text = node.textContent;
  node.textContent = ""; // Очистим узел

  var match, pos = 0;
  while (match = keywords.exec(text)) {
    var before = text.slice(pos, match.index);
    node.appendChild(document.createTextNode(before));
    var strong = document.createElement("strong");
    strong.appendChild(document.createTextNode(match[0]));
    node.appendChild(strong);
    pos = keywords.lastIndex;
  }
  var after = text.slice(pos);
  node.appendChild(document.createTextNode(after));
}

Функция highlightCode принимает узел <pre> и регулярку (с включённой настройкой global), совпадающую с ключевым словом языка программирования, которое содержит элемент.

Свойство textContent используется для получения всего текста узла, а затем устанавливается в пустую строку, что приводит к очищению узла. Мы в цикле проходим по всем вхождениям выражения keyword, добавляем между ними текст в виде простых текстовых узлов, а совпавший текст (ключевые слова) добавляем, заключая их в элементы <strong> (жирный шрифт).

Мы можем автоматически подсветить весь код страницы, перебирая в цикле все элементы <pre>, у которых есть атрибут data-language, и вызывая на каждом highlightCodeс правильной регуляркой.

var languages = {
  javascript: /\b(function|return|var)\b/g /* … etc */
};

function highlightAllCode() {
  var pres = document.body.getElementsByTagName("pre");
  for (var i = 0; i < pres.length; i++) {
    var pre = pres[i];
    var lang = pre.getAttribute("data-language");
    if (languages.hasOwnProperty(lang))
      highlightCode(pre, languages[lang]);
  }
}

Вот пример:

<p>А вот и она, функция идентификации:</p>
<pre data-language="javascript">
function id(x) { return x; }
</pre>

<script>highlightAllCode();</script>

Есть один часто используемый атрибут, class, имя которого является ключевым словом в JavaScript. По историческим причинам, когда старые реализации JavaScript не умели обращаться с именами свойств, совпадавшими с ключевыми словами, этот атрибут доступен через свойство под названием className. Вы также можете получить к нему доступ по его настоящему имени “class” через методы getAttribute и setAttribute.

Расположение элементов (layout)

Вы могли заметить, что разные типы элементов располагаются по-разному. Некоторые, типа параграфов <p> и заголовков <h1> растягиваются на всю ширину документа и появляются на отдельных строках. Такие элементы называют блочными. Другие, как ссылки <a> или акцентированный текст <strong> появляются на одной строчке с окружающим их текстом. Они называются встроенными (inline).

Для любого документа браузеры могут вычислить расположение элементов, раскладку, в которой у каждого элемента будет размер и положение на основе его типа и содержимого. Затем эта раскладка используется для создания внешнего вида документа.

Размер и положение элемента можно узнать через JavaScript. Свойства offsetWidth и offsetHeight выдают размер в пикселях, занимаемый элементом. Пиксель – основная единица измерений в браузерах, и обычно соответствует размеру минимальной точки экрана. Сходным образом, clientWidth и clientHeight дают размер внутренней части элемента, не считая бордюра (или, как говорят некоторые, поребрика).

<p style="border: 3px solid red">
  Я в коробочке
</p>

<script>
  var para = document.body.getElementsByTagName("p")[0];
  console.log("clientHeight:", para.clientHeight);
  console.log("offsetHeight:", para.offsetHeight);
</script>

Самый эффективный способ узнать точное расположение элемента на экране – метод getBoundingClientRect. Он возвращает объект со свойствами top, bottom, left, и right (сверху, снизу, слева и справа), которые содержат положение элемента относительно левого верхнего угла экрана в пикселях. Если вам надо получить эти данные относительно всего документа, вам надо прибавить текущую позицию прокрутки, которая содержится в глобальных переменных pageXOffset и pageYOffset.

Разбор документа – задача сложная. В целях быстродействия браузерные движки не перестраивают документ каждый раз после его изменения, а ждут так долго. как это возможно. Когда программа JavaScript, изменившая документ, заканчивает работу, браузеру надо будет просчитать новую раскладку страницы, чтобы вывести изменённый документ на экран. Когда программа запрашивает позицию или размер чего-либо, читая свойства типа offsetHeight или вызывая getBoundingClientRect, для предоставления корректной информации тоже необходимо рассчитывать раскладку.

Программа, которая периодически считывает раскладку DOM и изменяет DOM, заставляет браузер много раз пересчитывать раскладку, и в связи с этим будет работать медленно. В следующем примере есть две разные программы, которые строят линию из символов X шириной в 2000 пикс, и измеряют время работы.

<p><span id="one"></span></p>
<p><span id="two"></span></p>

<script>
  function time(name, action) {
    var start = Date.now(); // Текущее время в миллисекундах
    action();
    console.log(name, "заняло", Date.now() - start, "ms");
  }

  time("тупо", function() {
    var target = document.getElementById("one");
    while (target.offsetWidth < 2000)
      target.appendChild(document.createTextNode("X"));
  });
  // → тупо заняло 32 ms

  time("умно", function() {
    var target = document.getElementById("two");
    target.appendChild(document.createTextNode("XXXXX"));
    var total = Math.ceil(2000 / (target.offsetWidth / 5));
    for (var i = 5; i < total; i++)
      target.appendChild(document.createTextNode("X"));
  });
  // → умно заняло 1 ms
</script>

Стили

Мы видели, что разные элементы HTML ведут себя по-разному. Некоторые показываются в виде блоков, другие встроенные. Некоторые добавляют визуальный стиль – например, <strong> делает жирным текст и <a> делает текст подчёркнутым и синим.

Внешний вид картинки в теге <img> или то, что ссылка в теге <a> при клике открывает новую страницу, связано с типом элемента. Но основные стили, связанные с элементом, вроде цвета текста или подчёркивания, могут быть нами изменены. Вот пример использования свойства style (стиль):

<p><a href=".">Обычная ссылка</a></p>
<p><a href="." style="color: green">Зелёная ссылка</a></p>

Атрибут «style» может содержать одно или несколько объявлений свойств (color), за которым следует двоеточие и значение. В случае нескольких объявлений они разделяются точкой с запятой: “color: red; border: none”.

Много всякого можно изменить при помощи стилей. Например, свойство display контролирует, показывается ли элемент в блочном или встроенном виде.

Текст показан <strong>встроенным</strong>,
<strong style="display: block">в виде блока</strong>, и
<strong style="display: none">вообще не виден</strong>.

Блочный элемент выводится отдельным блоком на собственной строке, а последний вообще не виден – display: none отключает показ элементов. Таким образом можно прятать элементы. Обычно это предпочтительно полному удалению их из документа, потому что их легче потом при необходимости снова показать.

Код JavaScript может напрямую действовать на стиль элемента через свойство узла style. В нём содержится объект, имеющий свойства для всех свойств стилей. Их значения – строки, в которые мы можем писать для смены какого-то аспекта стиля элемента.

<p id="para" style="color: purple">
  Красотень
</p>

<script>
  var para = document.getElementById("para");
  console.log(para.style.color);
  para.style.color = "magenta";
</script>

Некоторые имена свойств стилей содержат дефисы, например font-family. Так как с ними неудобно было бы работать в JavaScript (пришлось бы писать style[«font-family»]), названия свойств в объекте стилей пишутся без дефиса, а вместо этого в них появляются прописные буквы: style.fontFamily.

Каскадные стили

Система стилей в HTML называется CSS (Cascading Style Sheets, каскадные таблицы стилей). Таблица стилей – набор стилей в документе. Его можно писать внутри тега <style>:


<style>
  strong {
    font-style: italic;
    color: gray;
  }
</style>
<p>Теперь <strong>текст тега strong</strong> наклонный и серый.</p>

«Каскадные» означает, что несколько правил комбинируются для получения окончательного стиля документа. В примере на стиль по умолчанию для <strong>, который делает текст жирным, накладывается правило из тега <style>, по которому добавляется font-style и цвет.

Когда значение свойства определяется несколькими правилами, приоритет остаётся у более поздних. Если бы стиль текста в <style> включал правило font-weight: normal, конфликтующее со стилем по умолчанию, то текст был бы обычный, а не жирный. Стили, которые применяются к узлу через атрибут style, имеют наивысший приоритет.

В CSS возможно задавать не только название тегов. Правило для .abc применяется ко всем элементам, у которых указан класс “abc”. Правило для #xyz применяется к элементу с атрибутом id равным “xyz” (атрибуты id необходимо делать уникальными для документа).

.subtle {
  color: gray;
  font-size: 80%;
}
#header {
  background: blue;
  color: white;
}
/* Элементы p, у которых указаны классы a и b, а id задан как main */
p.a.b#main {
  margin-bottom: 20px;
}

Приоритет самых поздних правил работает, когда у правил одинаковая детализация. Это мера того, насколько точно оно описывает подходящие элементы, определяемая числом и видом необходимых аспектов элементов. К примеру, правило для p.a более детально, чем правила для p или просто .a, и будет иметь приоритет над ними.

Запись p > a {…} применима ко всем тегам <a>, находящимся внутри тега <p> и являющимся его прямыми потомками.
Подобным образом p a {…} применимо также ко всем тегам <a> внутри <p>, при этом неважно, является ли <a> прямым потомком или нет.

Селекторы запросов

В этой книге мы не будем часто использовать таблицы стилей. Понимание их работы критично для программирования в браузере, но подробное разъяснение всех их свойств заняло бы 2-3 книги. Главная причина знакомства с ними и с синтаксисом селекторов (записей, определяющих, к каким элементам относятся правила) – мы можем использовать тот же эффективный мини-язык для поиска элементов DOM.

Метод querySelectorAll, существующий и у объекта document, и у элементов-узлов, принимает строку селектора и возвращает массивоподобный объект, содержащий все элементы, подходящие под него.

<p>Люблю грозу в начале
  <span class="animal">мая</span>,</p>
<p>Когда весенний, первый гром,</p>
<p>Как бы <span class="character">резвяся
  <span class="animal">и играя</span></span>,</p>
<p>Грохочет в небе голубом.</p>

<script>
  function count(selector) {
    return document.querySelectorAll(selector).length;
  }
  console.log(count("p"));           // Все элементы <p> 
  // → 4
  console.log(count(".animal"));     // Класс animal
  // → 2
  console.log(count("p .animal"));   // Класс animal внутри <p>
  // → 2
  console.log(count("p > .animal")); // Прямой потомок <p>
  // → 1
</script>

В отличие от методов вроде getElementsByTagName, возвращаемый querySelectorAll объект не интерактивный. Он не изменится, если вы измените документ.

Метод querySelector (без All) работает сходным образом. Он нужен, если вам необходим один конкретный элемент. Он вернёт только первое совпадение, или null, если совпадений нет.

Расположение и анимация

Свойство стилей position сильно влияет на расположение элементов. По умолчанию оно равно static, что означает, что элемент находится на своём обычном месте в документе. Когда оно равно relative, элемент всё ещё занимает место, но теперь свойства top и left можно использовать для сдвига относительно его обычного расположения. Когда оно равно absolute, элемент удаляется из нормального «потока» документа – то есть, он не занимает место и может накладываться на другие. Кроме того, его свойства left и top можно использовать для абсолютного позиционирования относительно левого верхнего угла ближайшего включающего его элемента, у которого position не равно static. А если такого элемента нет, тогда он позиционируется относительно документа.

Мы можем использовать это для создания анимации. Следующий документ показывает картинку с котом, которая двигается по эллипсу.

<p style="text-align: center">
  <img src="img/cat.png" style="position: relative">
</p>
<script>
  var cat = document.querySelector("img");
  var angle = 0, lastTime = null;
  function animate(time) {
    if (lastTime != null)
      angle += (time - lastTime) * 0.001;
    lastTime = time;
    cat.style.top = (Math.sin(angle) * 20) + "px";
    cat.style.left = (Math.cos(angle) * 200) + "px";
    requestAnimationFrame(animate);
  }
  requestAnimationFrame(animate);
</script>

Картинка отцентрирована на странице и ей задана position: relative. Мы постоянно обновляем свойства top и left картинки, чтобы она двигалась.

Скрипт использует requestAnimationFrame для вызова функции animate каждый раз, когда браузер готов перерисовывать экран. Функция animate сама опять вызывает requestAnimationFrame, чтобы запланировать следующее обновление. Когда окно браузера (или закладка) активна, это приведёт к обновлениям со скорость примерно 60 раз в секунду, что позволяет добиться хорошо выглядящей анимации.

Если бы мы просто обновляли DOM в цикле, страница бы зависла и ничего не было бы видно. Браузеры не обновляют страницу во время работы JavaScript, и не допускают в это время работы со страницей. Поэтому нам нужна requestAnimationFrame – она сообщает браузеру, что мы пока закончили, и он может заниматься своими браузерными вещами, например обновлять экран и отвечать на запросы пользователя.

Наша функция анимации передаётся текущее время через аргументы, которое оно сравнивает с предыдущим (переменная lastTime), чтобы движение кота было однородным, и анимация работала плавно. Если бы мы просто передвигали её на заданный промежуток на каждом шаге, движение бы запиналось если бы, например, другая задача загрузила бы компьютер.

Движение по кругу осуществляется с применением тригонометрических функций Math.cos и Math.sin. Я кратко опишу их для тех, кто с ними не знаком, так как они понадобятся нам в дальнейшем.

Math.cos и Math.sin полезны тогда, когда надо найти точки на окружности с центром в точке (0, 0) и радиусом в единицу. Обе функции интерпретируют свой аргумент как позицию на окружности, следуя против часовой стрелки, от нуля в самой правой точке, пока путь диной в 2π (около 6.28) не проведёт нас по кругу. Math.cos считает координату по оси x той точки, которая является нашей текущей позицией на окружности, а Math.sin выдаёт координату y. Позиции (или углы) больше, чем 2π или меньше чем 0, тоже допустимы – повороты повторяются так, что a+2π означает тот же самый угол, что и a.


Использование синуса и косинуса для вычисления координат

Анимация кота хранит счётчик angle для текущего угла поворота анимации, и увеличивает его пропорционально прошедшему времени каждый раз при вызове функции animation. Этот угол используется для подсчёта текущей позиции элемента image. Стиль top подсчитывается через Math.sin и умножается на 20 – это вертикальный радиус нашего эллипса. Стиль left считается через Math.cos и умножается на 200, так что ширина эллипса сильно больше высоты.

Стилям обычно требуются единицы измерения. В нашем случае приходится добавлять px к числу, чтобы объяснить браузеру, что мы считаем в пикселях (а не в сантиметрах, ems или других единицах). Это легко забыть. Использование чисел без единиц измерения приведёт к игнорированию стиля – если только число не равно 0, что не зависит от единиц измерения.

Итог

Программы JavaScript могут изучать и изменять текущий отображаемый браузером документ через структуру под названием DOM. Эта структура данных представляет модель документа браузера, а программа JavaScript может изменять её для изменения видимого документа. DOM организован в виде дерева, в котором элементы расположены иерархически в соответствии со структурой документа. У объектов элементов есть свойства типа parentNode и childNodes, которые используются для ориентирования на дереве.

Внешний вид документа можно изменять через стили, либо добавляя стили к узлам напрямую, либо определяя правила для каких-либо узлов. У стилей есть очень много свойств, таких, как color или display. JavaScript может влиять на стиль элемента напрямую через его свойство style.

Упражнения

Строим таблицу

Мы строили таблицы из простого текста в главе 6. HTML упрощает построение таблиц. Таблица в HTML строится при помощи следующих тегов:

<table>
  <tr>
    <th>name</th>
    <th>height</th>
    <th>country</th>
  </tr>
  <tr>
    <td>Kilimanjaro</td>
    <td>5895</td>
    <td>Tanzania</td>
  </tr>
</table>

Для каждой строки в теге <table> содержится тег <tr>. Внутри него мы можем размещать ячейки: либо ячейки заголовков <th>, либо обычные ячейки <td>.

Те же данные, что мы использовали в главе 6, снова доступны в переменной MOUNTAINS.

Напишите функцию buildTable, которая, принимая массив объектов с одинаковыми свойствами, строит структуру DOM, представляющую таблицу. У таблицы должна быть строка с заголовками, где имена свойств обёрнуты в элементы <th>, и должно быть по одной строчке на объект из массива, где его свойства обёрнуты в элементы <td>. Здесь пригодится функция Object.keys, возвращающая массив, содержащий имена свойств объекта.

Когда вы разберётесь с основами, выровняйте ячейки с числами по правому краю, изменив их свойство style.textAlign на «right».

<style>
  /* Определяет стили для красивых таблиц */
  table  { border-collapse: collapse; }
  td, th { border: 1px solid black; padding: 3px 8px; }
  th     { text-align: left; }
</style>

<script>
  function buildTable(data) {
    // Ваш код
  }

  document.body.appendChild(buildTable(MOUNTAINS));
</script>

Элементы по имени тегов

Метод getElementsByTagName возвращает все дочерние элементы с заданным именем тега. Сделайте свою версию этого метода в виде обычной функции, которая принимает узел и строчку (имя тега) и возвращает массив, содержащий все нисходящие узлы с заданным именем тега.

Чтобы выяснить имя тега элемента, используйте свойство tagName. Заметьте, что оно возвратит имя тега в верхнем регистре. Используйте методы строк toLowerCase или toUpperCase.

<h1>Заголовок с элементом <span>span</span> внутри.</h1>
<p>Параграф с <span>раз</span>, <span>два</span> элементами spans.</p>

<script>
  function byTagName(node, tagName) {
    // Ваш код
  }

  console.log(byTagName(document.body, "h1").length);
  // → 1
  console.log(byTagName(document.body, "span").length);
  // → 3
  var para = document.querySelector("p");
  console.log(byTagName(para, "span").length);
  // → 2
</script>

Шляпа кота

Расширьте анимацию кота, чтобы и кот и его шляпа <img src="img/hat.png"> летали по противоположным сторонам эллипса.

Или пусть шляпа летает вокруг кота. Или ещё что-нибудь интересное придумайте.

Чтобы упростить расположение множества объектов, неплохо будет переключиться на абсолютное позиционирование. Тогда top и left будут считаться относительно левого верхнего угла документа. Чтобы не использовать отрицательные координаты, вы можете добавить заданное число пикселей к значениям position.

<img src="img/cat.png" id="cat" style="position: absolute">
<img src="img/hat.png" id="hat" style="position: absolute">

<script>
  var cat = document.querySelector("#cat");
  var hat = document.querySelector("#hat");
  // Your code here.
</script>
  1. Простейший DOM
  2. Пример посложнее
  3. Пример с атрибутами и DOCTYPE
  4. Нормализация в различных браузерах
  5. Возможности, которые дает DOM
  6. Доступ к элементам

    1. document.documentElement
    2. document.body
    3. Типы DOM-элементов

      1. Пример
    4. Дочерние элементы
  7. Свойства элементов

    1. tagName
    2. style
    3. innerHTML
    4. className
    5. onclick, onkeypress, onfocus

Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) — объектная модель, используемая для XML/HTML-документов.

Согласно DOM-модели, документ является иерархией.
Каждый HTML-тег образует отдельный элемент-узел, каждый фрагмент текста — текстовый элемент, и т.п.

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

Простейший DOM

Построим, для начала, дерево DOM для следующего документа.

<html>
  <head>
    <title>Заголовок</title>
  </head>
  <body>
     Прекрасный документ
   </body>
</html>

Самый внешний тег — <html>, поэтому дерево начинает расти от него.

Внутри <html> находятся два узла: <head> и <body> — они становятся дочерними узлами для <html>.

простой DOM

Теги образуют узлы-элементы (element node). Текст представлен текстовыми узлами (text node). И то и другое — равноправные узлы дерева DOM.

Пример посложнее

Рассмотрим теперь более жизненную страничку:

<html>
    <head>
        <title>
            О лосях
        </title>
    </head>
    <body>
        Правда о лосях.
        <ol>
            <li>
                Лось - животное хитрое
            </li>
            <li>
                .. И коварное
            </li>
        </ol>
    </body>
</html>

Корневым элементом иерархии является html. У него есть два потомка. Первый — head, второй — body. И так далее, каждый вложенный тег является потомком тега выше:

На этом рисунке синим цветом обозначены элементы-узлы, черным — текстовые элементы.

Дерево образовано за счет синих элементов-узлов — тегов HTML.

А вот так выглядит дерево, если изобразить его прямо на HTML-страничке:

Кстати, дерево на этом рисунке не учитывает текст, состоящий из одних пробельных символов. Например, такой текстовый узел должен идти сразу после <ol>. DOM, не содержащий таких «пустых» узлов, называют «нормализованным».

Пример с атрибутами и DOCTYPE

Рассмотрим чуть более сложный документ.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>Документ</title>
    </head>
    <body>
        <div id="dataKeeper">Data</div>
        <ul>
            <li style="background-color:red">Осторожно</li>
            <li class="info">Информация</li>
        </ul>
        <div id="footer">Made in Russia &copy;</div>
    </body>
</html>

Верхний тег — html, у него дети head и body, и так далее. Получается дерево тегов:

Атрибуты

В этом примере у узлов есть атрибуты: style, class, id. Вообще говоря, атрибуты тоже считаются узлами в DOM-модели, родителем которых является элемент DOM, у которого они указаны.

Однако, в веб-программировании в эти дебри обычно не лезут, и считают атрибуты просто свойствами DOM-узла, которые, как мы увидим в дальнейшем, можно устанавливать и менять по желанию программиста.

DOCTYPE

Вообще-то это секрет, но DOCTYPE тоже является DOM-узлом, и находится в дереве DOM слева от HTML (на рисунке этот факт скрыт).

P.S. Насчет секрета — конечно, шутка, но об этом и правда далеко не все знают. Сложно придумать, где такое знание может пригодиться…

Нормализация в различных браузерах

При разборе HTML Internet Explorer сразу создает нормализованный DOM, в котором не создаются узлы из пустого текста.

Firefox — другого мнения, он создает DOM-элемент из каждого текстового фрагмента.
Поэтому в Firefox дерево этого документа выглядит так:

DOM дерево

На рисунке для краткости текстовые узлы обозначены просто решеткой. У body вместо 3 появилось 7 детей.

Opera тоже имеет чем похвастаться. Она может добавить лишний пустой элемент «просто от себя».

Чтобы это увидеть — откройте документ по этой ссылке. Он выдает число дочерних узлов document.body, включая текстовые узлы.

У меня получается 3 для IE, 7 для Firefox и 8 (!?) для Opera.

На практике эта несовместимость не создает больших проблем, но нужно о ней помнить. Например, разница может проявить себя в случае перебора узлов дерева.

Возможности, которые дает DOM

Зачем, кроме красивых рисунков, нужна иерархическая модель DOM?

Очень просто:

Каждый DOM-элемент является объектом и предоставляет свойства для манипуляции своим содержимым, для доступа к родителям и потомкам.

Для манипуляций с DOM используется объект document.
Используя document, можно получать нужный элемент дерева и менять его содержание.

Например, этот код получает первый элемент с тэгом ol, последовательно удаляет два элемента списка и затем добавляет их в обратном порядке:

var ol = document.getElementsByTagName('ol')[0]
var hiter = ol.removeChild(ol.firstChild)
var kovaren = ol.removeChild(ol.firstChild)
ol.appendChild(kovaren)
ol.appendChild(hiter)

Для примера работы такого скрипта — кликните на тексте на лосиной cтраничке

document.write

В старых руководствах и скриптах можно встретить модификацию HTML-кода страницы напрямую вызовом document.write.

В современных скриптах этот метод почти не используется, случаи его правильного применения можно пересчитать по пальцам.

Избегайте document.write.. Кроме случаев, когда вы действительно знаете, что делаете (а зачем тогда читаете самоучитель — вы и так гуру)

Разберем подробнее способы доступа и свойства элементов DOM.

Доступ к элементам

Любой доступ и изменения DOM берут свое начало от объекта document.

Начнем с вершины дерева.

document.documentElement

Самый верхний тег. В случае корректной HTML-страницы, это будет <html>.

document.body

Тег <body>, если есть в документе (обязан быть).

Это свойство работает немного по-другому, если установлен DOCTYPE Strict. Обычно проще поставить loose DOCTYPE.

Следующий пример при нажатии на кнопку выдаст текстовое представление объектов document.documentElement и document.body. Сама строка зависит от браузера, хотя объекты везде одни и те же.

<html>
  <body>
     <script>
       function go() {
         alert(document.documentElement)
         alert(document.body)
      }
     </script>
     <input type="button" onclick="go()" value="Go"/>
  </body>
</html>

Типы DOM-элементов

У каждого элемента в DOM-модели есть тип. Его номер хранится в атрибуте elem.nodeType

Всего в DOM различают 12 типов элементов.

Обычно используется только один: Node.ELEMENT_NODE, номер которого равен 1. Элементам этого типа соответствуют HTML-теги.

Иногда полезен еще тип Node.TEXT_NODE, который равен 3. Это текстовые элементы.

Остальные типы в javascript программировании не используются.

Следующий пример при нажатии на кнопку выведет типы document.documentElement, а затем тип последнего потомка узла document.body. Им является текстовый узел.

<html>
  <body>
     <script>
       function go() {
         alert(document.documentElement.nodeType)
         alert(document.body.lastChild.nodeType)         
      }
     </script>
     <input type="button" onclick="go()" value="Go"/>
     Текст
  </body>
</html>

Пример

Например, вот так выглядел бы в браузере документ из примера выше, если каждый видимый элемент обвести рамкой с цифрой nodeType в правом верхнем углу.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head><title>...</title></head>
    <body>
        <div id="dataKeeper">Data</div>
        <ul>
            <li style="background-color:red">Осторожно</li>
            <li class="info">Информация</li>
        </ul>
        <div id="footer">Made in Russia &copy;</div>
    </body>
</html>

DOM

Здесь показаны только элементы внутри body, т.к только они отображаются на странице. Для элементов типа 1 (теги) в скобочках указан соответствующий тег, для текстовых элементов (тип 3) — стоит просто цифра.

Дочерние элементы

С вершины дерева можно пойти дальше вниз. Для этого каждый DOM-узел содержит массив всех детей, отдельно — ссылки на первого и последнего ребенка и еще ряд полезных свойств.

  1. Все дочерние элементы, включая текстовые находятся в массиве childNodes.

    В следующем примере цикл перебирает всех детей document.body.

    for(var i=0; i<document.body.childNodes.length; i++) {
        var child = document.body.childNodes[i]
        alert(child.tagName) 
    }
    
  2. Свойства firstChild и lastChild показывают на первый и последний дочерние элементы и равны null, если детей нет.
  3. Свойство parentNode указывает на родителя. Например, для <body> таким элементом является <html>:

    alert(document.body.parentNode == document.documentElement) // true
    
  4. Свойства previousSibling и nextSibling указывают на левого и правого братьев узла.

В общем. если взять отдельно <body> с детьми из нормализованного DOM — такая картинка получается ОТ <body>:

И такая — для ссылок наверх и между узлами:

  • Синяя линия — массив childNodes
  • Зеленые линии — свойства firstChild, lastChild.
  • Красная линия — свойство parentNode
  • Бордовая и лавандовая линии внизу — previousSibling, nextSibling

Этих свойств вполне хватает для удобного обращения к соседям.

Свойства элементов

У DOM-элементов есть масса свойств. Обычно используется максимум треть из них. Некоторые из них можно читать и устанавливать, другие — только читать.

Есть еще и третий вариант, встречающийся в IE — когда устанавливать свойство можно только во время создания элемента.

Рассмотрим здесь еще некоторые (не все) свойства элементов, полезные при работе с DOM.

tagName

Атрибут есть у элементов-тегов и содержит имя тега в верхнем регистре, только для чтения.

Например,

alert(document.body.tagName) // => BODY

style

Это свойство управляет стилем. Оно аналогично установке стиля в CSS.

Например, можно установить element.style.width:

Исходный код этой кнопки:

<input
  type="button" 
  style="width: 300px" 
  onclick="this.style.width = parseInt(this.style.width)-10+'px'" 
  value="Укоротить на 10px"
/>

Обработчик события onclick обращается в этом примере к свойству this.style.width, т.к значением this в обработчике события является текущий элемент (т.е сама кнопка). Подробнее об этом — во введении в события.

Есть общее правило замены — если CSS-атрибут имеет дефисы, то для установки style нужно заменить их на верхний регистр букв.

Например, для установки свойства z-index в 1000, нужно поставить:

element.style.zIndex = 1000

innerHTML

Когда-то это свойство поддерживалось только в IE. Теперь его поддерживают все современные браузеры.

Оно содержит весь HTML-код внутри узла, и его можно менять.

Свойство innerHTML применяется, в основном, для динамического изменения содержания страницы, например:

document.getElementById('footer').innerHTML = '<h1>Bye!</h1> <p>See ya</p>'

Пожалуй, innerHTML — одно из наиболее часто используемых свойств DOM-элемента.

className

Это свойство задает класс элемента. Оно полностью аналогично html-атрибуту «class».

elem.className = 'newclass'

onclick, onkeypress, onfocus

.. И другие свойства, начинающиеся на «on…», хранят функции-обработчики соответствующих событий. Например, можно присвоить обработчик события onclick.

Подробнее об этих свойствах и обработчиках событий — см. введение в события.

The DOM Explained for Beginners – How the Document Object Model Works

When I started out as a web developer, people often threw around the term «DOM» in the industry. Every JavaScript tutorial mentioned it, but I didn’t know what it meant.

Fast forward two years, and now that I know what it’s all about, I am going to explain what the Document Object Model is in plain and simple English.

What is the DOM?

Imagine this: you have the TV on. You don’t like the show that’s being streamed, and you want to change it. You also want to increase its volume.

To do that, there has to be a way for you to interact with your television. And what do you use to do that?

A remote.

The remote serves as the bridge which allows you interact with your television.

You make the TV active and dynamic via the remote. And in the same way, JavaScript makes the HTML page active and dynamic via the DOM.

Just like how the television can’t do much for itself, JavaScript doesn’t do much more than allow you to‌‌ perform some calculations or work with basic strings.

So to make an HTML document more interactive and dynamic, the script‌‌ needs to be able to access the contents of the document and it also needs to know when the user is interacting with it.‌‌

It does this by communicating with the browser using the properties, methods, and events in the interface called the Document Object Model, or DOM.

For example, say that you want a button to change colours when it gets clicked or an image to slide when the mouse hovers over it. First, you need to reference those elements from your JavaScript.

The DOM is a tree-like representation of the web page that gets loaded into the browser.

It represents the web page using a‌‌ series of objects. The main object is the document object, which in turn houses other objects which also house their own objects, and so on.

The Document Object

This is the top most object in the DOM. It has properties and methods which you can use to get information about the document using a rule known as dot notation.

The Document Object Model Tree

Document Tree. Source https://w3.org

After the document, you place a dot followed by a property or method.

Let’s look at a simple demonstration that shows how a script can access the contents of an HTML document through the DOM:

<h1>Login to your account</h1>‌‌
<form name=”LoginFrm” action=”login.php” method=”post”>‌‌Username 
    <input type=”text” name=”txtUsername” size=”15”/> <br/>‌‌Password 
    <input type=”password” name=”numPassword” size=”15”/> <br/>‌‌
    <input type=”submit” value=”Log In” />‌‌
</form>‌‌
<p> New user? <a href=”register.php”> Register here</a> 
<a href=”lostPassword.php”> Retrieve password </a> 
</p>
var username = document.LoginFrm.txtUsername.value //Gets the username input 

Alright. That’s the HTML a login form. You can access all of these elements in JavaScript with the set of properties and methods the DOM API provides. But what are those methods?

In addition to the property and method included in the code snippet, let’s take a look at some of the other popular ones:

The querySelectorAll() method

You use this method to access one or more elements from the DOM that matches one or more CSS selectors:

<div> first div </div>
<p> first paragraph </p>
<div> second div </p>
<p> second paragraph </p>
<div> another div </div>
var paragraphs = document.querySelectorAll( 'p' );
paragraphs.forEach(paragraph => paragraph.display = 'none')

The createElement() method

You use this method to create a specified element and insert it into the DOM:

<div>first div</div>
<p> first paragraph</p> 
<div>second div</div>
<p>second paragraph</p> 
<div>another div</div>
var thirdParagraph = document.createElement('p');

The getElementById() method

You use this method to get an element from the document by its unique id attribute:

<div id='first'> first div </div> 
<p> first paragraph</p>
<div>second div</div>
<p> second paragraph</p>
<div>another div</div> 
var firstDiv = getElementById("first")

The getElementsByTagname() method

You use this method to access one or more elements by their HTML tag name:

<div> first div </div> 
<p> first paragraph</p> 
<div> second div</div> 
<p>second paragraph</p> 
<div>another div</div>
divs = document.getElementByTagname("div");

The appendChild() element

You use this element to access one or more elements by their HTML tag name.

It adds an element as the last child to the HTML element that invokes this method.

The child to be inserted can be either a newly created element or an already existing one. If it already exists, it will be moved from its previous position to the position of the last child.

<div
     <h2>Mangoes</h1>
</div>
var p = document.createElement( 'p' );
var h2 = document.querySelector( 'h2' );
var div = document.querySelector( 'div' );
h1.textContent = 'Mangoes are great...'
div.appendChild('p');

The innerHTML property

You use this property to access the text content of an element.

The addEventListener() property

This property attaches an event listener to your element.

It takes a callback which will run when that event is triggered.

<button>Click to submit</button>‌‌
var btn = document.querySelector( 'button' );‌‌
btn.addEventListener( 'click' ,foo);‌‌
function foo() { alert( 'submitted!' ); 
  				btn.innerHTML = '';
          }

The replaceChild() property

This property replaces one child element with another new or existing child element. If it already exists, it will be moved from its previous position to the position of the last child.

<div>‌‌
    <h1>Mangoes‌</h1>‌
</div>
var h2 = document.createElement( 'h2' );‌‌
var h1 = document.querySelector( 'h1' );‌‌
var div = document.querySelector( 'div' );‌‌
h2.textContent = 'Apple';‌‌
div.insertBefore(h1, h2);

The setAttribute() method

You use this method to set or change the value of an element’s attribute.

Suppose we have an attribute “id” containing the value “favourite.”‌‌ But we want to change the value to “worst” Here’s how you can do that with code:

<div>‌‌
    <h1 id='favourite'>Mangoes‌‌</h1>
</div>
var h1 = document.querySelector( 'h1' );
h1.setAttribute(div, 'worst');

The node method

Every element in an HTML page is known as a node.

You can access any element by using the following properties with the node object:

  • node.childNodes – accesses the child nodes of a selected parent‌‌
  • node.firstChild – accesses the first child of a selected parent‌‌
  • node.lastChild – accesses the last child of a selected parent.‌‌
  • node.parentNode – accesses the parent of a selected child node.‌‌
  • node.nextSibling – accesses the next consecutive element (sibling) of a selected element.‌‌
  • node.previousSibling – accesses the previous element (sibling) of a selected element
<ul id-“list”>‌‌
    <li><a href= ”about.html”‌‌class = ”list_one”> About‌‌</a></li>‌‌
    <li><a href= ”policy.html”> Policy‌‌</a></ li>‌‌
    <li><a href= ”map.html”> Map‌‌</a></ li>‌‌
    <li><a href= ”Refund.html”> Refund‌‌</a></li>‌‌
</ul>
var list = document.getElementsById( “site-list” )‌‌
var firstItem = list‌‌.childNodes[0].childNodes[0];

Summary

The DOM is a top down representation of all the elements that make up a web page. It’s the interface through which your script interacts with your HTML.

There are many properties and methods which you can use to get information about the DOM and manipulate it.

That’s all for this article. I hope you learnt something worthwhile.

If you liked it, you can buy me some coffee here.

Thank you and see you soon.



Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started

Iqra Masroor

Iqra Masroor

Posted on

• Updated on



 



 



 



 



 

DOM or Document Object Model is a representation of the web page or document, which can be modified with a scripting language such as JavaScript according to MDN.

It characterizes the DOM HTML document as a hierarchical tree structure and each element in the document tree is called a Node.

Alt Text

Alt Text

DOM nodes represent all components that make up a web page. For example, a head tag is considered to be a node. Most nodes have a starting tag and ending tag. Things can be nested inside these tags. The inner node is called a child node and the outer node is considered to be its parent node.

Some nodes are self-closing tags like the «img» tag. These are called void nodes and cannot be a parent node, meaning things can’t be nested within them.

Refer to the graph below.
Alt Text

Since ‘document’ is an object which has properties and attributes, it will have properties & methods. In order to access things within an object, we use selector and query methods to change the content displayed in the browser.

Element Selectors

document.getElementById("idName")

//This method only returns the one element by the specified ID. 

document.getElementByClass("className")
//This method returns all elements inside the whole document by the class you specified.

document.getElementById("someElement").getElementsByClassName("className")
//It works in all elements of the DOM, this will return all elements by the class you specify inside the element you want

Enter fullscreen mode

Exit fullscreen mode

Query Selectors

document.querySelector("#idName")

//This method takes one argument, which is a CSS selector & returns the first element that matches the selector. 

document.querySelectorAll(".className")

//Works similar to above; returns a node list collection of all matching elements.

Enter fullscreen mode

Exit fullscreen mode

Create an Element

APPEND

document.createElement("body")
//this method creats the element, but it does not show up on the page.

document.body.append(element)
//this method gets the element to appear on the page.

Enter fullscreen mode

Exit fullscreen mode

.INNERHTML

<h1 id="greetings"> HELLO </h1>

let element = document.querySelector("#greeting")
element.innerHTML = "Welcome"
//selects the h1 called greetings and changes HELLO to welcome

Enter fullscreen mode

Exit fullscreen mode

Changing Attributes

const element = document.querySelector(".container")

element.style.backgroundColor="#f0f0f0"
//changes the selected elements(in this case the container class) color to grey

Enter fullscreen mode

Exit fullscreen mode

Removing Elements

element.remove()
//removes a whole element from the page

Enter fullscreen mode

Exit fullscreen mode

This is just a basic overview of some of the methods used to manipulate the DOM.

For many years we have relied on third-party JavaScript libraries such as jQuery to write JavaScript for the web. However, in recent years, the DOM API has evolved a lot, so adding dynamic functionality to web pages using native JavaScript is becoming easier for developers.

This article is a high-level overview of the DOM API built into every modern web browser. We’ll look into what the DOM API is, how it’s related to JavaScript, and how you can use it to write JavaScript for the web.

What Is the DOM?

DOM stands for Document Object Model, and it’s the data representation of a web document, such as an HTML page, an SVG graphic, or an XML file.

DOMDOMDOM

In very plain English, the DOM is a bunch of objects (Document Object Model) that make up a web page. Each object is the real-world implementation of an interface — the blueprint that describes what an object should look like. Thus, one interface can be implemented by multiple objects.

For example, each HTML <form> element is represented by a HTMLFormElement object in the DOM, which is an implementation of the HTMLFormElement interface. This interface comes with pre-written properties (interface variables) and methods (interface functions) that you can use to add dynamic functionality to each form object (that appears as a <form> tag in the HTML document). For example, you can use HTMLFormElement.reset() method to reset a form to its initial state.

Essentially, that’s how the entire DOM works. The most basic interface is called Node — every HTML element, text string, and the document itself is a node too. Other interfaces extend the Node interface and inherit its properties and methods, but as each interface can have children, inheritance works at multiple levels. For example, HTMLFormElement is a child interface of HTMLElement, which is a child interface of Element, which is a child interface of Node.

The web browser arranges all of these nodes into a document tree (DOM tree) that represents the nodes and the relationships between them as a tree hierarchy.

DOM treeDOM treeDOM tree

Courtesy of Birger Erikkson; Source

What is the DOM API?

While there are many DOMs (each HTML, XML, and SVG document has a DOM), there’s just one DOM API, which is a W3C specification. The DOM API is written in JavaScript, and you can use it to manipulate the DOM of a web document using JavaScript.

API stands for “Application Programming Interface”, and it’s a collection of classes, interfaces, properties, methods, and other code structures that developers can use to access the functionality of an application. For example, you can use the Twitter API to access tweets, users, messages, ads, and other elements of the Twitter application programmatically. Similarly, you can use web APIs to use the built-in functionalities of a web browser (which is an application too).

the dom apithe dom apithe dom api

The DOM API is one of the multiple web APIs built into web browsers. There are lower-level APIs such as the Web Workers API (for background operations) and higher-level ones such as the DOM.

Most web APIs, including the DOM API, are written in JavaScript. First, the W3C creates the specifications, then browser vendors implement them. Thus, “browser support” refers to whether a browser has implemented a specific functionality of the DOM API or not. If browser support is good, you can safely use an object (interface implementation), property, or method in production, while if it’s poor, it’s better to find an alternative (you can check browser support on the CanIUse website). 

HTML tags and attributes and CSS styles all have their handles in the DOM API so that you can modify them dynamically. For example, the following HTML tag: 

1
<a href='page.html' target='_blank'>Click this link</a>

is represented by four nodes (one parent and three children) in the DOM:

  • (parent node) an HTMLAnchorElement object in the DOM (that’s the implementation of the HTMLAnchorElement interface); this is an element node
  • (child node) an HTMLAnchorElement.href property defined by the HTMLAnchorElement interface; this is an attribute node
  • (child node) a HTMLAnchorElement.target property; this is another attribute node
  • (child node) a text node (“Click this link”)

Now, let’s see how you can use the DOM API in practice.

An Example of Using the DOM API

Let’s imagine you want to change the text color of the link above dynamically from the default blue to magenta.

If you analyze the documentation of the HTMLAnchorElement interface, you won’t find any properties or methods that you can use for this purpose.

But, as HTMLAnchorElement is a child interface of HTMLElement, it inherits its properties and methods too. This interface has an HTMLElement.style property that lets you change the CSS belonging to an HTML element. If you read its description in the docs, you’ll see that it can use properties from the CSS Properties Reference, also defined by the DOM API.

In our example, we’ll need to use the color property, so we’ll write the following code:

1
element.style.color = 'magenta';

However, if we run this code, it will return a ReferenceError exception in the console because element is not defined. Before we can manipulate a DOM element with JavaScript, we need to select it using the DOM API and pass it to a variable.

To select nodes in a document, we’ll need to go to the Document interface. This interface has two methods we can use, Document.querySelector() that finds the first instance of a selector and Document.querySelectorAll() that finds all instances of a selector. Here, we’ll use querySelector(), as we have just one anchor element on the page. So, the new code is:

1
// selects the first 'a' element on the page

2
let element = document.querySelector('a');
3

4
// sets the 'color' property to 'magenta'

5
element.style.color = 'magenta';

Now, this code will work properly and change the link color to magenta. There are a few things to note here:

  • as style.color is a property, we can set its value by using the assignment operator (= sign)
  • as document.querySelector() is a method (indicated by the parentheses after it), we can define its value by adding a parameter ('a') within the parentheses
  • while we need to define the element variable, we don’t have to define the document variable as it’s a global object similar to window (the belonging variable is built into the browser — note that the object is written with capital initial e.g. Document, while the variable is written with all lowercase e.g. document)
  • if we had more than one link on the page, we’d need to use the querySelectorAll() method instead of querySelector() and place the element.style.color declaration within a for loop to change the color of all links on the page:
1
<a href='page01.html' target='_blank'>Link 01</a>
2
<a href='page02.html' target='_blank'>Link 02</a>
3
<a href='page03.html' target='_blank'>Link 03</a>
1
// selects all 'a' elements on the page

2
let elements = document.querySelectorAll('a');
3

4
// loops through the element object which is a NodeList

5
for (let i = 0; i < elements.length; i++) {
6

7
    // sets the 'color' property to 'magenta'

8
    elements[i].style.color = 'magenta';
9
}

We need to loop through the elements variable because querySelectorAll() returns a NodeList object that includes more than one item.

What Type of Object is Returned?

It’s always important to know what type of object a method returns, as it defines your options. For instance, as querySelector() returns an Element object that has just one element, we didn’t have to loop through it in the previous example. 

You can either check the return type in the method’s documentation or by using the console.log() method and run the code in the console of your browser’s DevTools.

For instance, you can check the return type of the elements variable above with console.log(elements); which returns the following value in the web console (in Chrome DevTools):

Check object type in web consoleCheck object type in web consoleCheck object type in web console

A Better Example of Using the DOM API

It frequently happens that the DOM API provides more than one solution for the same task. So, it’s always a good idea to ask whether we have used the best solution. For instance, in the example above we didn’t; we changed the style using JavaScript, which goes against the separation of concerns (SoC) software design principle.

It would be much more effective, both performance- and workflow-wise, if we added a class dynamically to each anchor tag we want to change the color of, then add the style rules using CSS. Luckily, the DOM has a solution for this, respectively the Element.classList.add() method that we can use on every object that inherits from the generic Element object.

The above example looks like this using this method:

1
// selects all 'a' elements on the page

2
let elements = document.querySelectorAll('a');
3

4
// loops through the elements variable

5
for (let i = 0; i < elements.length; i++) {
6

7
    // adds the 'dynamic-color' class to each element

8
    elements[i].classList.add('dynamic-color');
9
}

Then, we can add the color with CSS:

1
.dynamic-color {
2
    color: magenta;
3
}

Much nicer, I think. It’ll also be easier to change the link color if we want to.

Next Steps

Here ends our high-level overview of how the DOM API works!

To understand it better, have a look at my other articles on this subject: one about event listeners that let you bind dynamic functionality (e.g. the link color change in the example above) to events (e.g. a click) and another one about how to convert jQuery to JavaScript using the DOM API.

The Mozilla Developer Network also has great explainer articles, such as their Introduction to the DOM tutorial and the HTML DOM API page that explains how to use interfaces belonging to HTML element nodes.

Did you find this post useful?

Anna Monus

Web developer & technical writer. Covering web design, web development, UX design, and accessibility.

Понравилась статья? Поделить с друзьями:
  • Верапамил гель 15 процентный инструкция по применению взрослым
  • Тест коллективное руководство 11 класс
  • Как очистить внутреннюю память на телефоне хонор 7а пошаговая инструкция
  • Тролза 5275 оптима руководство по эксплуатации
  • Эльтон лекарство в желтой упаковке инструкция по применению