034.4 Урок 1
Сертифікат: |
Основи веброзробки |
---|---|
Версія: |
1.0 |
Розділ: |
034 JavaScript-програмування |
Тема: |
034.4 JavaScript-маніпулювання контентом та стилями вебсайту |
Урок: |
1 з 1 |
Вступ
HTML, CSS і JavaScript – це три різні технології, які працюють разом в Інтернеті. Щоб створити справді динамічні та інтерактивні сторінки, програміст JavaScript повинен поєднувати компоненти з HTML і CSS під час виконання, що значно полегшується за допомогою Document Object Model (DOM).
Взаємодія з DOM
DOM – це структура даних, яка працює як програмний інтерфейс до документа, де кожен аспект документа представлений у вигляді вузла в DOM, і кожна зміна, внесена в DOM, негайно відбивається в документі. Щоб показати, як використовувати DOM у JavaScript, збережіть наступний HTML-код у файлі під назвою example.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Маніпулювання HTML за допомогою JavaScript</title>
</head>
<body>
<div class="content" id="content_first">
<p>Динамічний контент знаходиться тут</p>
</div><!-- #content_first -->
<div class="content" id="content_second" hidden>
<p>Другий розділ</p>
</div><!-- #content_second -->
</body>
</html>
DOM буде доступним лише після завантаження HTML, тому напишіть наступний JavaScript в кінці тіла сторінки (перед кінцевим тегом </body>
):
<script>
let body = document.getElementsByTagName("body")[0];
console.log(body.innerHTML);
</script>
Об’єкт document
є верхнім елементом DOM, усі інші елементи відходять від нього. Метод getElementsByTagName()
перелічує всі елементи, що походять із document
, які мають задану назву тегу. Незважаючи на те, що тег body
використовується лише один раз у документі, метод getElementsByTagName()
завжди повертає подібну до масиву колекцію знайдених елементів, отже використовується індекс [0]
для повернення першого (і єдиного) знайденого елемента.
HTML контент
Як показано в попередньому прикладі, елемент DOM, повернутий document.getElementsByTagName("body")[0]
, був призначений змінній body
. Змінну body
можна використовувати для керування елементом body сторінки, оскільки вона успадковує всі методи та атрибути DOM від цього елемента. Наприклад, властивість innerHTML
містить весь код розмітки HTML, написаний всередині відповідного елемента, тому її можна використовувати для читання внутрішньої розмітки. Наш виклик console.log(body.innerHTML)
друкує вміст, що всередині між тегами <body></body>
на вебконсоль. Змінну також можна використовувати для заміни цього вмісту, як у body.innerHTML = "<p>Контент стерто</p>"
.
Замість того, щоб змінювати цілі частини розмітки HTML, практичніше зберігати структуру документа без змін і просто взаємодіяти з його елементами. Після того, як документ відтворюється браузером, усі елементи стають доступними за допомогою методів DOM. Наприклад, можна створити список і отримати доступ до всіх елементів HTML за допомогою спеціального рядка *
в методі getElementsByTagName()
об’єкта document
:
let elements = document.getElementsByTagName("*");
for ( element of elements )
{
if ( element.id == "content_first" )
{
element.innerHTML = "<p>Новий контент</p>";
}
}
Цей код помістить усі елементи, знайдені в document
, до змінної elements
. Змінна elements
є об’єктом, подібним до масиву, тому ми можемо перебирати кожен з його елементів за допомогою циклу for
. Якщо на HTML-сторінці, на якій виконується цей код, є елемент з атрибутом id
, встановленим в content_first
(див. зразок сторінки HTML, показаний на початку уроку), оператор if
відповідає цьому елементу, а його вміст розмітки буде бути змінено на <p>Новий контент</p>
. Зауважте, що атрибути елемента HTML у DOM доступні за допомогою dot нотації властивостей об’єкта JavaScript: отже, element.id
посилається на атрибут id
поточного елемента циклу for
. Також можна використовувати метод getAttribute()
, як у element.getAttribute("id")
.
Немає необхідності перебирати всі елементи, якщо ви хочете перевірити лише їх підмножину. Наприклад, метод document.getElementsByClassName()
обмежує відповідні елементи тими, що мають конкретний клас:
let elements = document.getElementsByClassName("content");
for ( element of elements )
{
if ( element.id == "content_first" )
{
element.innerHTML = "<p>Новий контент</p>";
}
}
Однак ітерація багатьох елементів документа за допомогою циклу – не найкраща стратегія, коли потрібно змінити певний елемент на сторінці.
Вибір конкретних елементів
JavaScript надає оптимізовані методи для вибору елемента, над яким ви хочете працювати. Попередній цикл можна повністю замінити методом document.getElementById()
:
let element = document.getElementById("content_first");
element.innerHTML = "<p>Новий контент</p>";
Кожен атрибут id
в документі має бути унікальним, тому метод document.getElementById()
повертає лише один об’єкт DOM. Навіть оголошення змінної element
можна пропустити, оскільки JavaScript дає змогу нам безпосередньо об’єднувати методи:
document.getElementById("content_first").innerHTML = "<p>Новий контент</p>";
Метод getElementById()
є кращим методом для визначення розташування елементів у DOM, оскільки його продуктивність набагато вища, ніж ітераційних методів під час роботи зі складними документами. Однак не всі елементи мають явний ID, і метод повертає значення null, якщо жоден елемент не збігається з наданим ID (це також запобігає використанню ланцюжкових атрибутів або функцій, як-от innerHTML
, використаний у прикладі вище). Більше того, практичніше призначити атрибути ID лише основним компонентам сторінки, а потім використовувати селектори CSS, щоб знайти їхні дочірні елементи.
Селектори, представлені в попередньому уроці з CSS, є шаблонами, які відповідають елементам у DOM. Метод querySelector()
повертає перший відповідний елемент у дереві DOM, тоді як querySelectorAll()
повертає всі елементи, які відповідають вказаному селектору.
У попередньому прикладі метод getElementById()
отримує елемент, який має ID content_first
. Метод querySelector()
може виконувати те саме завдання:
document.querySelector("#content_first").innerHTML = "<p>Новий контент</p>";
Оскільки метод querySelector() використовує синтаксис селектора, наданий ID має починатися з хеша. Якщо відповідний елемент не знайдено, метод querySelector()
повертає null.
У попередньому прикладі весь вміст div content_first
було замінено наданим текстовим рядком. Рядок містить HTML-код, що не вважається найкращою практикою. Ви повинні бути обережними, додаючи жорстко закодовану HTML-розмітку до коду JavaScript, тому що елементи відстеження можуть стати складними, коли потрібно змінити загальну структуру документа.
Селектори не обмежуються ID елемента. До внутрішнього елемента p
можна звертатися безпосередньо:
document.querySelector("#content_first p").innerHTML = "Новий контент";
Селектор #content_first p
відповідатиме лише першому елементу p
всередині div #content_first
. Це добре працює, якщо ми хочемо маніпулювати першим елементом. Однак ми можемо захотіти змінити другий абзац:
<div class="content" id="content_first">
<p>Не змінюйте цей абзац.</p>
<p>Динамічний контент знаходиться тут.</p>
</div><!-- #content_first -->
У цьому випадку ми можемо використовувати псевдоклас :nth-child(2)
для відповідності другому елементу p
:
document.querySelector("#content_first p:nth-child(2)").innerHTML = "Новий контент";
Число 2
в p:nth-child(2)
вказує на другий абзац, який відповідає селектору. Перегляньте урок про CSS-селектори, щоб дізнатися більше про селектори та як ними користуватися.
Робота з атрибутами
Здатність JavaScript взаємодіяти з DOM не обмежується лише маніпулюванням вмістом. Дійсно, найбільш поширеним використанням JavaScript у браузері є зміна атрибутів існуючих елементів HTML.
Скажімо, наша оригінальна сторінка прикладу HTML тепер має три розділи вмісту:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Маніпулювання HTML за допомогою JavaScript</title>
</head>
<body>
<div class="content" id="content_first" hidden>
<p>Перший розділ.</p>
</div><!-- #content_first -->
<div class="content" id="content_second" hidden>
<p>Другий розділ.</p>
</div><!-- #content_second -->
<div class="content" id="content_third" hidden>
<p>Третій розділ.</p>
</div><!-- #content_third -->
</body>
</html>
Ви можете захотіти зробити лише один з розділів видимим за раз, отже, атрибут hidden
присутній у всіх тегах div
. Це корисно, наприклад, для показу лише одного зображення з галереї зображень. Щоб зробити один з них видимим під час завантаження сторінки, додайте на сторінку такий код JavaScript:
//Який контент показувати
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
document.querySelector(content_visible).removeAttribute("hidden");
Вираз, оцінений оператором switch
, випадковим чином повертає число 0, 1 або 2. Відповідний ID-селектор потім призначається змінній content_visible
, яка використовується методом querySelector(content_visible)
. Зв’язаний виклик removeAttribute("hidden")
видаляє атрибут hidden
з елемента.
Можливий і протилежний підхід: спочатку всі розділи можуть бути видимими (без атрибута hidden
), а потім програма JavaScript може призначити атрибут hidden
кожному розділу, крім розділу в content_visible
. Для цього потрібно перебрати всі елементи div вмісту, які відрізняються від вибраного, що можна зробити за допомогою методу querySelectorAll()
:
// Який контент показувати
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
// Приховати контент всіх елементів div, крім content_visible
for ( element of document.querySelectorAll(".content:not("+content_visible+")") )
{
// Hidden є логічним атрибутом, тому будь-яке значення вмикає його
element.setAttribute("hidden", "");
}
Якщо для змінної content_visible
встановлено значення #content_first
, селектором буде .content:not(#content_first)
, який можна прочитати як усі елементи, що мають клас content
, крім тих, що мають ID content_first
. Метод setAttribute()
додає або змінює атрибути елементів HTML. Його перший параметр – це ім’я атрибута, а другий – значення атрибута.
Однак правильний спосіб змінити зовнішній вигляд елементів – це CSS. У цьому випадку ми можемо встановити для властивості CSS display
значення hidden
, а потім змінити його на block
за допомогою JavaScript:
<style>
div.content { display: none }
</style>
<div class="content" id="content_first">
<p>Перший розділ.</p>
</div><!-- #content_first -->
<div class="content" id="content_second">
<p>Другий розділ.</p>
</div><!-- #content_second -->
<div class="content" id="content_third">
<p>Третій розділ.</p>
</div><!-- #content_third -->
<script>
// Який контен показувати
let content_visible;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_visible = "#content_first";
break;
case 1:
content_visible = "#content_second";
break;
case 2:
content_visible = "#content_third";
break;
}
document.querySelector(content_visible).style.display = "block";
</script>
Ті самі передові практики, які застосовуються до змішування тегів HTML з JavaScript, застосовуються і до CSS. Тому прописувати властивості CSS безпосередньо в коді JavaScript не рекомендується. Натомість правила CSS слід писати окремо від коду JavaScript. Правильний спосіб альтернативного візуального стилю – вибрати попередньо визначений клас CSS для елемента.
Робота з класами
Елементи можуть мати більше одного пов’язаного класу, що полегшує написання стилів, які можна додавати або видаляти за потреби. Було б виснажливо змінювати багато атрибутів CSS безпосередньо в JavaScript, тому ви можете створити новий клас CSS із цими атрибутами, а потім додати клас до елемента. Елементи DOM мають властивість classList
, яку можна використовувати для перегляду й маніпулювання класами, призначеними для відповідного елемента.
Наприклад, замість того, щоб змінювати видимість елемента, ми можемо створити додатковий клас CSS, щоб виділити наш content
div:
div.content {
border: 1px solid black;
opacity: 0.25;
}
div.content.highlight {
border: 1px solid red;
opacity: 1;
}
Ця таблиця стилів додасть тонку чорну межу та напівпрозорість до всіх елементів, що мають клас content
. Тільки елементи, які також мають клас highlight
, будуть повністю непрозорими та матимуть тонку червону рамку. Тоді замість того, щоб безпосередньо змінювати властивості CSS, як ми робили раніше, ми можемо використовувати метод classList.add("highlight")
у вибраному елементі:
// Який контент виділити
let content_highlight;
switch ( Math.floor(Math.random() * 3) )
{
case 0:
content_highlight = "#content_first";
break;
case 1:
content_highlight = "#content_second";
break;
case 2:
content_highlight = "#content_third";
break;
}
// Виділіть вибраний div
document.querySelector(content_highlight).classList.add("highlight");
Усі методи та приклади, які ми бачили досі, були виконані в кінці процесу завантаження сторінки, але вони не обмежуються цим етапом. Насправді те, що робить JavaScript настільки корисним для веброзробників, – це його здатність реагувати на події на сторінці, що ми розглянемо далі.
Обробники подій
Усі видимі елементи сторінки чутливі до інтерактивних подій, таких як клацання або рух самої миші. Ми можемо пов’язувати із цими подіями спеціальні дії, що значно розширює можливості HTML-документа.
Ймовірно, найочевиднішим елементом HTML, який отримує вигоду від пов’язаної дії, є елемент button
. Щоб показати, як це працює, додайте три кнопки над першим елементом div
сторінки прикладу:
<p>
<button>Перший</button>
<button>Другий</button>
<button>Третій</button>
</p>
<div class="content" id="content_first">
<p>Перший розділ.</p>
</div><!-- #content_first -->
<div class="content" id="content_second">
<p>Другий розділ.</p>
</div><!-- #content_second -->
<div class="content" id="content_third">
<p>Третій розділ.</p>
</div><!-- #content_third -->
Кнопки самі по собі нічого не роблять, але припустимо, що ви хочете виділити div
, що відповідає натиснутій кнопці. Ми можемо використовувати атрибут onClick
, щоб пов’язати дію з кожною кнопкою:
<p>
<button onClick="document.getElementById('content_first').classList.toggle('highlight')">Перший</button>
<button onClick="document.getElementById('content_second').classList.toggle('highlight')">Другий</button>
<button onClick="document.getElementById('content_third').classList.toggle('highlight')">Третій</button>
</p>
Метод classList.toggle()
додає вказаний клас до елемента, якщо він відсутній, і видаляє цей клас, якщо він вже присутній. Якщо ви запустите приклад, ви помітите, що одночасно можна виділити більше одного div
. Щоб виділити лише div
, що відповідає натиснутій кнопці, необхідно видалити клас highlight
з інших елементів div
. Тим не менш, якщо користувацька дія занадто довга або містить більше одного рядка коду, практичніше написати функцію окремо від тегу елемента:
function highlight(id)
{
// Видалити клас "highlight" з усіх елементів контенту
for ( element of document.querySelectorAll(".content") )
{
element.classList.remove('highlight');
}
// Додати клас "highlight" до відповідного елементу
document.getElementById(id).classList.add('highlight');
}
Як і попередні приклади, цю функцію можна розмістити всередині тегу <script>
або у зовнішньому файлі JavaScript, пов’язаному з документом. Функція highlight
спочатку видаляє клас highlight
з усіх елементів div
, пов’язаних з класом content
, а потім додає клас highlight
до вибраного елемента. Кожна кнопка повинна викликати цю функцію зі свого атрибута onClick
, використовуючи відповідний ID як аргумент функції:
<p>
<button onClick="highlight('content_first')">Перший</button>
<button onClick="highlight('content_second')">Другий</button>
<button onClick="highlight('content_third')">Третій</button>
</p>
Додатково до атрибута onClick
, ми можемо використовувати атрибут onMouseOver
(викликається, коли вказівний пристрій використовується для переміщення курсору на елемент), атрибут onMouseOut
(спрацьовує, коли вказівний пристрій більше не міститься на елементі) тощо. Крім того, обробники подій не обмежуються кнопками, тому ви можете призначити користувацькі дії цим обробникам подій для всіх видимих елементів HTML.
Вправи до посібника
-
Використовуючи метод
document.getElementById()
, як ви можете вставити фразу “Динамічний вміст” до внутрішнього вмісту елемента, ID якого єmessage
? -
Яка різниця між покликанням на елемент за його ID за допомогою методу
document.querySelector()
і використанням методуdocument.getElementById()
? -
Яка мета методу
classList.remove()
? -
Яким буде результат використання методу
myelement.classList.toggle("active")
, якщоmyelement
не має призначеного для нього класуactive
?
Дослідницькі вправи
-
Який аргумент методу
document.querySelectorAll()
змусить його імітувати методdocument.getElementsByTagName("input")
? -
Як ви можете використовувати властивість
classList
, щоб вивести всі класи, пов’язані із цим елементом?
Підсумки
Цей урок розповідає про те, як використовувати JavaScript для зміни вмісту HTML та його CSS-властивостей за допомогою DOM (об’єктної моделі документа). Ці зміни можуть бути викликані подіями користувача, що корисно для створення динамічних інтерфейсів. Урок охоплює такі поняття та процедури:
-
Як перевірити структуру документа за допомогою таких методів, як
document.getElementById()
,document.getElementsByClassName()
,document.getElementsByTagName()
,document.querySelector()
таdocument.querySelectorAll()
. -
Як змінити контент документа за допомогою властивості
innerHTML
. -
Як додавати та змінювати атрибути елементів сторінки за допомогою методів
setAttribute()
таremoveAttribute()
. -
Правильний спосіб маніпулювання класами елементів за допомогою властивості
classList
та його зв’язку зі стилями CSS. -
Як прив’язати функції до подій миші в певних елементах.
Відповіді до вправ посібника
-
Використовуючи метод
document.getElementById()
, як ви можете вставити фразу “Динамічний вміст” до внутрішнього вмісту елемента, ID якого єmessage
?Це можна зробити за допомогою властивості
innerHTML
:document.getElementById("message").innerHTML = "Динамічний вміст"
-
Яка різниця між покликанням на елемент за його ID за допомогою методу
document.querySelector()
і використанням методуdocument.getElementById()
?ID має супроводжуватися хеш-символом у функціях, які використовують селектори, наприклад
document.querySelector()
. -
Яка мета методу
classList.remove()
?Він видаляє клас (ім’я якого вказано як аргумент функції) з атрибута
class
відповідного елемента. -
Яким буде результат використання методу
myelement.classList.toggle("active")
, якщоmyelement
не має призначеного для нього класуactive
?Метод призначить
myelement
класactive
.
Відповіді до дослідницьких вправ
-
Який аргумент методу
document.querySelectorAll()
змусить його імітувати методdocument.getElementsByTagName("input")
?Використання
document.querySelectorAll("input")' відповідатиме всім елементам `input
на сторінці, таким якdocument.getElementsByTagName("input")
. -
Як ви можете використовувати властивість
classList
, щоб вивести всі класи, пов’язані із цим елементом?Властивість
classList
є об’єктом, подібним до масиву, тому циклfor
можна використовувати для ітерації по всіх класах, які він містить.