Модифікації DOM є ключем до створення “живих” сторінок.
Тут ми побачимо, як створювати нові елементи “на льоту” та змінювати ті, що вже існують.
Приклад: показати повідомлення
Розглянемо на прикладі. Ми додамо на сторінку повідомлення яке виглядатиме краще аніж alert
.
Ось так воно виглядатиме:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert">
<strong>Привіт!</strong> Ви прочитали важливе повідомлення.
</div>
Це був приклад HTML розмітки. Тепер створимо такий самий div
за допомогою JavaScript (припускаємо, що стилі вже є в HTML або в окремому CSS-файлі).
Створення елементу
Є два способи створення DOM вузлів:
document.createElement(tag)
-
Створює новий елемент з заданим тегом:
let div = document.createElement('div');
document.createTextNode(text)
-
Створює новий текстовий вузол з заданим текстом:
let textNode = document.createTextNode('От і я');
У більшості випадків нам потрібно створювати саме елементи, такі як div
для повідомлень.
Створення повідомлення
Створення div елементу для повідомлення складається з трьох кроків:
// 1. Створюємо елемент <div>
let div = document.createElement('div');
// 2. Задаємо йому клас "alert"
div.className = "alert";
// 3. Наповнюємо його змістом
div.innerHTML = "<strong>Всім привіт!</strong> Ви прочитали важливе повідомлення.";
Ми створили елемент, але поки що він знаходиться лише у змінній з назвою div
, тому не можемо бачити його на сторінці, оскільки він не є частиною документа.
Методи вставки
Щоб div
з’явився нам потрібно вставити його десь в document
. Наприклад, в елемент <body>
який можна отримати звернувшись до document.body
.
Для цього існує спеціальний метод append
: document.body.append(div)
.
Ось повний код:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Всім привіт!</strong> Ви прочитали важливе повідомлення.";
document.body.append(div);
</script>
Тут ми викликали метод append
на document.body
, але це можна зробити на будь-якому іншому елементі, щоб вставити інший елемент в нього. Наприклад, ми можемо додати щось до <div>
викликавши div.append(anotherElement)
.
Ось більше методів вставки, вони вказують куди саме буде вставлено вміст:
node.append(...вузли або рядки)
– додає вузли або рядки в кінецьnode
,node.prepend(...вузли або рядки)
– вставляє вузли або рядки на початкуnode
,node.before(...вузли або рядки)
– вставляє вузли або рядки попередуnode
,node.after(...вузли або рядки)
– вставляє вузли або рядки післяnode
,node.replaceWith(...вузли або рядки)
– замінюєnode
заданими вузлами або рядками.
Аргументами цих методів є довільний список DOM вузлів або текстові рядки(які автоматично перетворюються на текстові вузли).
Подивимося на них в дії.
Ось приклад використання цих методів для додавання елементів до списку та тексту до/після нього:
<ol id="ol">
<li>0</li>
<li>1</li>
<li>2</li>
</ol>
<script>
ol.before('before'); // вставити рядок "before" перед <ol>
ol.after('after'); // вставити рядок "after" після <ol>
let liFirst = document.createElement('li');
liFirst.innerHTML = 'prepend';
ol.prepend(liFirst); // вставити liFirst на початку <ol>
let liLast = document.createElement('li');
liLast.innerHTML = 'append';
ol.append(liLast); // вставити liLast в кінці <ol>
</script>
Наочна ілюстрація роботи методів:
Отже, в підсумку список буде таким:
before
<ol id="ol">
<li>prepend</li>
<li>0</li>
<li>1</li>
<li>2</li>
<li>append</li>
</ol>
after
Як вже було сказано, ці методи можуть вставляти декілька вузлів і фрагментів тексту за один виклик.
Наприклад, тут вставлено рядок та елемент одночасно.
<div id="div"></div>
<script>
div.before('<p>Привіт!</p>', document.createElement('hr'));
</script>
Пам’ятайте, що текст вставляється «як текст», а не «як HTML», з відповідними замінами таких символів як <
, >
.
Тому фінальний HTML буде таким:
<p>Привіт</p>
<hr>
<div id="div"></div>
Іншими словами, рядки вставляються безпечним способом, як це робить elem.textContent
.
Тому ці методи можна використовувати лише для вставки DOM вузлів або фрагментів тексту.
Але що, як нам потрібно вставити рядок HTML «як html», з усіма тегами та іншим, так само як це робить elem.innerHTML
?
insertAdjacentHTML/Text/Element
Для цього ми можемо використовувати інший, досить універсальний метод: elem.insertAdjacentHTML(where, html)
.
Перший параметр це кодове слово, яке вказує куди вставляти відносно elem
. Його значення має бути одним з наступних:
"beforebegin"
– вставитиhtml
безпосередньо передelem
,"afterbegin"
– вставитиhtml
вelem
, на початку,"beforeend"
– вставитиhtml
вelem
, в кінці,"afterend"
– вставитиhtml
безпосередньо післяelem
.
Другим параметром є рядок HTML, який вставляється “як HTML”.
Наприклад:
<div id="div"></div>
<script>
div.insertAdjacentHTML('beforebegin', '<p>Привіт</p>');
div.insertAdjacentHTML('afterend', '<p>Бувай</p>');
</script>
…виглядатиме як:
<p>Привіт</p>
<div id="div"></div>
<p>Бувай</p>
Ось так ми можемо додавати HTML на сторінку.
На зображенні показані всі можливі варіанти вставки.
Можна легко помітити схожість між цим та попереднім зображенням. Місця вставки насправді ті ж самі, але останній метод вставляє HTML.
Метод має двох братів:
elem.insertAdjacentText(куди, текст)
– синтаксис той самий, але рядок тексту вставляється «як текст» замість HTMLelem.insertAdjacentElement(куди, текст)
– синтаксис той самий, але вставляється елемент
Наведені методи існують для того, щоб синтаксис залишався “однорідним”. На практиці найчастіше використовується insertAdjacentHTML
. Тому що для вставки елементів та тексту є методи append/prepend/before/after
– вони коротші для написання і так само вміють вставляти вузли чи фрагменти тексту.
Отже, ось альтернативний варіант показу повідомлення:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
document.body.insertAdjacentHTML("afterbegin", `<div class="alert">
<strong>Всім привіт!</strong> Ви прочитали важливе повідомлення.
</div>`);
</script>
Видалення вузлів
Щоб видалити вузол використовуйте метод node.remove()
.
Спробуємо зробити так, щоб наше повідомлення зникало через одну секунду:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<script>
let div = document.createElement('div');
div.className = "alert";
div.innerHTML = "<strong>Всім привіт!</strong> Ви прочитали важливе повідомлення.";
document.body.append(div);
setTimeout(() => div.remove(), 1000);
</script>
Зверніть увагу: якщо ми хочемо перемістити елемент на інше місце в документі – тоді немає потреби його видаляти.
Всі методи вставки автоматично видаляють вузол з попереднього місця.
Наприклад, поміняємо елементи місцями:
<div id="first">Перший</div>
<div id="second">Другий</div>
<script>
// немає потреби викликати remove
second.after(first); // взяти #second та після нього вставити #first
</script>
Клонування вузлів: cloneNode
Як вставити ще одне схоже повідомлення?
Можна створити функцію та помістити код в неї. Але є ще один спосіб – клонувати наявний div
та змінити текст всередині (якщо потрібно).
У випадку, коли наш елемент великий, це може бути швидше та простіше.
- Виклик
elem.cloneNode(true)
створює «глибоку» копію елемента – з усіма атрибутами та піделементами. Якщо ми викличемоelem.cloneNode(false)
, тоді буде створена копія без дочірніх елементів.
Приклад копіювання повідомлення:
<style>
.alert {
padding: 15px;
border: 1px solid #d6e9c6;
border-radius: 4px;
color: #3c763d;
background-color: #dff0d8;
}
</style>
<div class="alert" id="div">
<strong>Всім привіт!</strong> Ви прочитали важливе повідомлення.
</div>
<script>
let div2 = div.cloneNode(true); // клонувати елемент
div2.querySelector('strong').innerHTML = 'Bye there!'; // змінити клона
div.after(div2); // вставити клонований елемент після існуючого `div`
</script>
DocumentFragment
DocumentFragment
це спеціальний DOM-вузол який служить обгорткою для передачі списку вузлів.
Ми можемо додавати до нього інші вузли, але коли ми вставляємо його кудись, він “зникає”, а замість нього вставляється лише його вміст (контент).
Наприклад, getListContent
нижче генерує фрагмент з елементами <li>
, які пізніше вставляються в <ul>
:
<ul id="ul"></ul>
<script>
function getListContent() {
let fragment = new DocumentFragment();
for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
fragment.append(li);
}
return fragment;
}
ul.append(getListContent()); // (*)
</script>
Зверніть увагу, що в останньому рядку коду (*)
ми додаємо DocumentFragment
, але він “зникає”, тому в результаті структура буде такою:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
DocumentFragment
рідко використовується явно. Навіщо додавати до вузла особливого типу, якщо натомість можна повернути масив вузлів? Переписаний приклад:
<ul id="ul"></ul>
<script>
function getListContent() {
let result = [];
for(let i=1; i<=3; i++) {
let li = document.createElement('li');
li.append(i);
result.push(li);
}
return result;
}
ul.append(...getListContent()); // append + оператор "..." = друзі!
</script>
Ми згадуємо DocumentFragment
в основному тому, що на ньому базуються деякі концепції, як от елемент template, який ми розберемо пізніше.
Застарілі методи вставки/видалення
Також є застарілі методи для маніпуляції з DOM, які існують лише з історичних причин.
Ці методи прийшли з давніх часів. Сьогодні немає жодних причин їх використовувати, оскільки сучасні методи, такі як append
, prepend
, before
, after
, remove
, replaceWith
набагато зручніші у використанні.
Ми перераховуємо ці методи лише тому, що вони можуть зустрітися вам в багатьох старих скриптах:
parentElem.appendChild(node)
-
Додає
node
як останній дочірній елементparentElem
.В наведеному нижче прикладі додаємо
<li>
в кінець<ol>
:<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Привіт, світ!'; list.appendChild(newLi); </script>
parentElem.insertBefore(node, nextSibling)
-
Вставляє
node
передnextSibling
вparentElem
.В наведеному нижче прикладі вставляється новий елемент списку перед другим
<li>
:<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Привіт, світ!'; list.insertBefore(newLi, list.children[1]); </script>
Щоб вставити
newLi
першим елементом, ми можемо зробити так:list.insertBefore(newLi, list.firstChild);
parentElem.replaceChild(node, oldChild)
-
Замінює
oldChild
наnode
поміж дочірніми елементамиparentElem
. parentElem.removeChild(node)
-
Видаляє
node
зparentElem
(припускаючи, щоnode
– це його дочірній елемент).В наведеному нижче прикладі з
<ol>
видаляється перший<li>
:<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let li = list.firstElementChild; list.removeChild(li); </script>
Всі ці методи повертаються вставлені/видалено вузли. Іншими словами, parentElem.appendChild(node)
повертає node
. Але зазвичай повернуті значення не використовуються, ми лише запускаємо метод.
Декілька слів про «document.write»
Є ще один дуже застарілий метод додати вміст на вебсторінку: document.write
.
Синтаксис:
<p>Десь на сторінці...</p>
<script>
document.write('<b>Привіт від JS</b>');
</script>
<p>Кінець</p>
Виклик document.write(html)
записує html
на сторінку “прямо тут і зараз”. Рядок html
може бути згенерований динамічно, тож метод досить гнучкий. За допомогою JavaScript ми можемо створити повноцінну вебсторінку та записати її вміст в документ.
Метод прийшов з тих часів коли не було ні DOM, ні стандартів… Справді давні часи. Метод досі живий, тому що є скрипти, в яких він використовується.
В сучасних скриптах він рідко зустрічається через наступні важливі обмеження:
Виклик document.write
працює лише під час завантаження сторінки.
Якщо ми викличемо його пізніше, то чинний вміст документа буде видалений.
Наприклад:
<p>Через одну секунду вміст цієї сторінки буде замінено...</p>
<script>
// document.write через 1 секунду
// виклик відбувся після того як сторінка завантажилася, тому метод стирає вміст
setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>
Тому цей метод непридатний на стадії «після завантаження», на відміну від інших DOM-методів які ми розібрали раніше.
Це його недолік.
Окрім того, є і перевага. Технічно, коли document.write
викликається поки браузер читає (“розбирає”) вхідний HTML і щось записує в документ, то браузер сприймає його так, наче він з самого початку був там, в HTML-документі.
Тому він працює надзвичайно швидко, адже не відбувається жодних DOM-модифікацій. Він записує безпосередньо в текст сторінки, поки DOM ще не сформований.
Отже, якщо нам потрібно динамічно додати в HTML велику кількість тексту, і ми на стадії завантаження сторінки, і швидкість завантаження має значення – цей метод може допомогти. Але на практиці всі ці вимоги рідко поєднуються. І зазвичай цей метод зустрічається в скриптах лише тому, що вони старі.
Підсумки
-
Методи для створення нових вузлів:
document.createElement(tag)
– створює елемент з заданим тегом,document.createTextNode(value)
– створює текстовий вузол (рідко використовується),elem.cloneNode(deep)
– клонує елемент, якщоdeep==true
, то з усіма нащадками.
-
Вставка та видалення:
node.append(...nodes or strings)
– вставляє вnode
, в кінець,node.prepend(...nodes or strings)
– вставляє вnode
, на початку,node.before(...nodes or strings)
– вставляє прямо передnode
,node.after(...nodes or strings)
– вставляє відразу післяnode
,node.replaceWith(...nodes or strings)
– замінюєnode
.node.remove()
– видаляєnode
.
Текстові рядки вставляються «як текст».
-
Також є застарілі методи:
parent.appendChild(node)
parent.insertBefore(node, nextSibling)
parent.removeChild(node)
parent.replaceChild(newElem, node)
Всі вони повертають
node
. -
Метод
elem.insertAdjacentHTML(where, html)
вставляє заданий HTML в залежності від значення параметраwhere
:"beforebegin"
– вставляєhtml
прямо передelem
,"afterbegin"
– вставляєhtml
вelem
, на початку,"beforeend"
– вставляєhtml
вelem
, в кінці,"afterend"
– вставляєhtml
відразу післяelem
.
Також є схожі методи,
elem.insertAdjacentText
таelem.insertAdjacentElement
, що вставляють текстові рядки та елементи, але їх рідко використовують. -
Щоб додати HTML на сторінку до того як вона повністю завантажиться:
document.write(html)
Після завантаження сторінки такий виклик призведе до стирання документа. В основному зустрічається в старих скриптах.
Коментарі
<code>
, для кількох рядків – обгорніть їх тегом<pre>
, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)