20 серпня 2023 р.

Стилі та класи

Перед тим як перейти до того, як JavaScript взаємодіє зі стилями та класами, є одне важливе правило. Сподіваємося, воно достатньо очевидне, проте ми все одно повинні це згадати.

Загалом існує два способи стилізації елемента:

Створити клас в CSS і додати його до елемента: <div class="..."> 2. Записати правила безпосередньо в атрибут style: <div style="...">.

JavaScript дозволяє змінювати як класи, так і style властивості.

Зазвичай ми маємо віддавати перевагу CSS-класам над style. Пряме задання стилів слід використовувати лише в тих випадках, якщо класи “не можуть з цим впоратися”.

Для прикладу, допустимо використовувати style, якщо ми динамічно обчислюємо координати елемента та хочемо встановлювати їх через JavaScript, як от:

let top = /* складні обрахунки */;
let left = /* складні обрахунки */;

elem.style.left = left; // наприклад, '123px', розраховується під час виконання
elem.style.top = top; // наприклад '456px'

В інших випадках, як-от зробити текст червоним або додати іконку на фон – опишіть це в CSS, а потім додайте клас (JavaScript легко з цим впорається). Такий підхід більш гнучкий, і простіший у підтримці.

className та classList

Зміна класу є однією з найчастіше використовуваних дій у скриптах.

В давні часи JavaScript мав певні обмеження: зарезервоване слово, таке як "class", не могло бути властивістю об’єкта. Цього обмеження наразі більше немає, проте на той час було неможливо мати властивість "class", як от elem.class.

Тому для класів було додано схожу на вигляд властивість "className": elem.className відповідає атрибуту "class".

Наприклад:

<body class="main page">
  <script>
    alert(document.body.className); // main page
  </script>
</body>

Коли ми присвоюємо щось властивості elem.className, це значення замінює весь рядок класів. Інколи це саме те, що нам потрібно, проте значно частіше ми хочемо просто додати чи видалити один клас.

Для цього існує інша властивість, а саме elem.classList.

Властивість elem.classList – це спеціальний об’єкт, який містить методи для додавання, видалення або перемикання окремого класу.

Наприклад:

<body class="main page">
  <script>
    // додати клас
    document.body.classList.add('article');

    alert(document.body.className); // main page article
  </script>
</body>

Таким чином можна оперувати як цілим рядком класів за допомогою властивості className, так і окремими класами через classList. Що саме обрати – залежить від потреб конкретної ситуації.

Методи classList:

  • elem.classList.add/remove("class") – додати/видалити клас.
  • elem.classList.toggle("class") – додає клас, якщо він не існує, інакше видаляє його.
  • elem.classList.contains("class") – перевіряє, чи існує переданий клас, відповідно повертає true/false.

Крім того, classList – це ітерований об’єкт, тому можна легко вивести перелік всіх класів циклом for..of, як показано далі:

<body class="main page">
  <script>
    for (let name of document.body.classList) {
      alert(name); // main, а за ним page
    }
  </script>
</body>

Стилі елемента

Властивість elem.style – це об’єкт, вміст якого відповідає тому, що записано в атрибуті "style". Встановлення elem.style.width="100px" працює точнісінько так само, як рядок width:100px записаний в атрибут style.

Для властивостей, які називаються кількома словами, використовується верблюдячий регістр (camelCase):

background-color  => elem.style.backgroundColor
z-index           => elem.style.zIndex
border-left-width => elem.style.borderLeftWidth

Наприклад:

document.body.style.backgroundColor = prompt('background color?', 'green');
Властивості з префіксами

Властивості з браузерними префіксами – наприклад, -moz-border-radius, -webkit-border-radius також підпорядковуються цьому правилу: дефіс означає верхній регістр.

Наприклад:

button.style.MozBorderRadius = '5px';
button.style.WebkitBorderRadius = '5px';

Скидання властивості в elem.style

Інколи ми хочемо призначити певну стильову властивість, а пізніше видалити її.

Наприклад, щоб приховати елемент, ми можемо встановити властивість elem.style.display = "none".

Потім може виникнути потреба видалити style.display так, як наче вона не була встановлена. Замість delete elem.style.display ми повинні присвоїти їй порожній рядок: elem.style.display = "".

// якщо ми запустимо цей код, елемент <body> "моргне"
document.body.style.display = "none"; // сховати

setTimeout(() => document.body.style.display = "", 1000); // назад до нормального стану

Якщо ми встановлюємо для style.display порожній рядок, то браузер застосовує CSS-класи та свої вбудовані стилі нормально, ніби такої властивості як style.display взагалі не було.

Також для цього існує спеціальний метод elem.style.removeProperty('style property'). Отже, ми можемо видалити таку властивість наступним чином:

document.body.style.background = 'red'; // ставить колір red до background

setTimeout(() => document.body.style.removeProperty('background'), 1000); // видаляє background через 1 секунду
Повний перезапис за допомогою style.cssText

Зазвичай, style.* використовується для встановлення окремих властивостей стилю. Немає можливості задати весь стиль, як от div.style="color: red; width: 100px", оскільки div.style – це об’єкт, і він доступний лише для читання.

Існує спеціальна властивість style.cssText, яка дає змогу встановлювати повний стиль елемента як рядок:

<div id="div">Кнопка</div>

<script>
  // тут можна встановити такі спеціальні прапорці стилю, як "important"
  div.style.cssText=`color: red !important;
    background-color: yellow;
    width: 100px;
    text-align: center;
  `;

  alert(div.style.cssText);
</script>

Ця властивість рідко використовується, оскільки присвоєння стилів у такий спосіб видаляє всі наявні стилі: тобто воно не додає, а замінює їх. Може випадково видалити щось потрібне. Однак можна спокійно використовувати її для нових елементів, коли наперед відомо, що там немає ніяких наявних стилів, які можна було б випадково видалити.

Того самого можна досягнути шляхом встановлення значення атрибута: div.setAttribute('style', 'color: red...').

Пам’ятайте про одиниці вимірювання

Не забувайте додавати одиниці вимірювання CSS до значень.

Наприклад, ми повинні встановлювати elem.style.top не на 10, а на 10px. Інакше воно не працюватиме:

<body>
  <script>
    // не працює!
    document.body.style.margin = 20;
    alert(document.body.style.margin); // '' (порожній рядок, присвоєння проігноровано)

    // тепер додамо одиниці виміру CSS (px) - і це працює
    document.body.style.margin = '20px';
    alert(document.body.style.margin); // 20px

    alert(document.body.style.marginTop); // 20px
    alert(document.body.style.marginLeft); // 20px
  </script>
</body>

Зауважте, будь ласка: браузер в останніх рядках “розпаковує” властивість style.margin і від неї обчислює значення властивостей style.marginLeft та style.marginTop.

Обчислені стилі: getComputedStyle

Отже, змінити стиль легко. Проте як його прочитати?

Уявімо собі, що ми хочемо дізнатися розміри, зовнішні відступи, та колір елементу. Як це зробити?

Властивість style працює лише зі значенням атрибута "style", без врахування CSS-каскаду.

Тобто за допомогою elem.style неможливо прочитати щось, що прийшло з CSS-класів.

Наприклад, ось тут властивість style не бачить зовнішніх відступів:

<head>
  <style> body { color: red; margin: 5px } </style>
</head>
<body>

  Червоний текст
  <script>
    alert(document.body.style.color); // порожньо
    alert(document.body.style.marginTop); // порожньо
  </script>
</body>

…Але що робити, якщо ми хочемо, скажімо, збільшити розмір зовнішнього відступу на 20px? Для цього нам потрібно знати актуальне значення.

Для цього існує інший метод: getComputedStyle.

Синтаксис виглядає так:

getComputedStyle(element, [pseudo])
element
Елемент, значення властивості якого потрібно прочитати.
pseudo
Вказується, якщо потрібен стиль псевдоелемента, наприклад: ::before. Порожній рядок, або опущений аргумент означатимуть, що буде опрацьовано сам елемент.

В результаті виклику методу буде повернено об’єкт зі стилями, подібно до elem.style, але зі врахуванням всіх класів CSS.

Наприклад:

<head>
  <style> body { color: red; margin: 5px } </style>
</head>
<body>

  <script>
    let computedStyle = getComputedStyle(document.body);

    // тепер звідти можна прочитати значення зовнішнього відступу та кольору

    alert( computedStyle.marginTop ); // 5px
    alert( computedStyle.color ); // rgb(255, 0, 0)
  </script>

</body>
Обчислені (computed) і кінцеві (resolved) значення властивостей

CSS має дві концепції:

  1. Обчислене (computed) значення стилю – це значення після застосування всіх CSS-правил і наслідування, результат CSS-каскаду. Воно може виглядати як height:1em чи font-size:125%.
  2. Кінцеве (resolved) значення стилю – це значення, яке безпосередньо застосовується до елементу. Такі значення, як 1em чи 125% – відносні. Браузер бере обчислене значення, і перераховує все у фіксованих і абсолютних одиницях, наприклад: height:20px чи font-size:16px. Для геометричних властивостей кінцеві значення можуть бути числами з рухомою комою, як от width:50.5px.

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

Тому станом на сьогодні метод getComputedStyle насправді повертає кінцеве значення властивості, для геометрії зазвичай виражене в px.

getComputedStyle вимагає повної назви властивості

Слід завжди запитувати точну назву властивості, значення якої потрібно отримати, як paddingLeft, marginTop чи borderTopWidth. Інакше коректний результат не гарантовано.

Наприклад, якщо на елементі задано властивості paddingLeft/paddingTop, що ми отримаємо, запитавши значення getComputedStyle(elem).padding? Нічого, чи може якесь “згенероване” значення наявних полів? Стандарту для цього не існує.

Стилі, які застосовані на :visited (відвідані) посилання – приховуються!

Відвідані посилання можна пофарбувати, використовуючи CSS-псевдоклас :visited.

Проте метод getComputedStyle не дає доступу до цього кольору, бо інакше будь-яка вебсторінка могла б визначити, чи користувач відвідував якесь посилання, просто створивши це посилання на сторінці та перевіривши його стилі.

JavaScript не бачить стилі, застосовані через селектор :visited. На додачу, також є певні обмеження в CSS, які забороняють застосовувати на :visited будь-які стилі, що змінюють геометрію елемента. Це все робиться з метою гарантувати, що для шкідливої сторінки не існує ніякого побічного способу перевірити, чи посилання відвідувалось, і таким чином поламати приватність користувача.

Підсумки

Існує дві властивості DOM для керування класами:

  • className – рядкове значення, гарно підходить для керування всім набором класів елемента в цілому.
  • classList – об’єкт з методами add/remove/toggle/contains, підходить для роботи з окремими класами.

Для зміни стилів:

  • Властивість style – об’єкт зі стилями, записаними верблюдячим регістром (camelCase). Читання і запис у неї має такий самий зміст, як і модифікація окремих властивостей в атрибуті "style". Щоб побачити, як застосовується important та інші рідкісні речі – на MDN описано перелік методів.

  • Властивість style.cssText показує атрибут "style" в цілому, весь рядок стилів.

Для зчитування кінцевих стилів (з врахуванням всіх класів, після застосування всього CSS і обчислення остаточних значень):

  • Метод getComputedStyle(elem, [pseudo]) повертає об’єкт, подібний до властивості style, з кінцевими значеннями. Виключно для читання.

Завдання

важливість: 5

Напишіть функцію showNotification(options), яка створює сповіщення <div class="notification"> з переданим вмістом. Сповіщення повинно автоматично зникати через 1.5 секунди.

Вхідні параметри такі:

// показує елемент з текстом "Hello" біля правого верхнього кутка вікна
showNotification({
  top: 10, // 10px від верха вікна (усталено має бути 0px)
  right: 10, // 10px від правого краю вікна (усталено — 0px)
  html: "Hello!", // HTML-код сповіщення
  className: "welcome" // додатковий клас для елемента div (необов'язково)
});

Демонстрація в новому вікні

Використовуйте позиціонування CSS для розміщення елемента за вказаними координатами top/right. Документ з оточенням вже містить всі необхідні стилі.

Відкрити пісочницю для завдання.

Навчальна карта