У специфікації CSS з'явилася нова функція progress(), що зіставляє задане значення з певним діапазоном. Найчастіше її застосовують для масштабування макетів, проте можливості цим не обмежуються — далі розглянемо, як за допомогою progress() реалізувати умовну стилізацію елементів на основі логіки, схожої на булеву.
Принцип роботи progress()
progress(current value, lower bound, upper bound)
Функція приймає поточне значення, яке потрібно оцінити відносно діапазону, заданого нижньою межею (початкове значення) та верхньою межею (кінцеве значення).
(current value - lower bound) / (upper bound - lower bound)
Логіка обчислення проста. Коли поточне значення менше або дорівнює нижній межі, функція повертає 0. Якщо значення більше або дорівнює верхній межі — результат дорівнює 1.
Якщо ж значення потрапляє в проміжок між межами, функція видає пропорційне число від 0 до 1.
When current value = upper bound progress(cv, lb, ub) progress(16px, 9px, 12px) = 1 (clamped) When current value is between lower and upper bounds progress(cv, lb, ub) progress(500px, 400px, 600px) = 0.5
У чому переваги progress()
- Функція спрощує обчислення в межах діапазону значень.
- Вона повертає число, з яким можна проводити операції для будь-яких обчислюваних типів даних (кути, відсотки тощо), або напряму передавати його властивостям, що приймають числа, наприклад opacity.
- За замовчуванням значення за межами діапазону обмежуються (clamp) до 0 або 1, що дозволяє реалізовувати щось подібне до булевої логіки.
Функція progress() корисна в багатьох сценаріях: гнучка типографіка, стилізація на основі розміру контейнера чи вʼюпорту, а також адаптивні анімації.
Факт: стандартне обмеження (clamping) у progress() можна вимкнути за допомогою ключового слова no-clamp. Наприклад, progress(no-clamp 10, 20, 60) поверне -0.25 замість 0, яке було б повернуто при увімкненому обмеженні. Наразі підтримку no-clamp має лише Firefox.
Три частини діапазону
Окрім перетворення поточного значення на пропорційний показник від 0 до 1, progress() виконує ще одну важливу дію, з якої, власне, усе й починається.
Функція формує три зони: область інтерполяції, нижню зону, обмежену за замовчуванням, і верхню зону, теж обмежену за замовчуванням.
У CSS іноді потрібно визначити, чи набір значень перебуває вище або нижче певного порогу.
Уявімо приклад: користувачам пропонують вписати назви фільмів, які вони недавно дивилися. За це нараховують бейджі різних рівнів кіноглядацької активності. Якщо вписано три фільми, користувач отримує бейдж Casual Viewer. За шість фільмів — Flick Fanatic, а за девʼять і більше — Film Buff.
Під капотом це означає порівняння кількості вписаних фільмів із кожним рівнем бейджа. Якщо знайдено відповідність, показується цей бейдж разом з усіма вже здобутими раніше.
Інакше кажучи, потрібно вибрати підмножину бейджів для поточного рівня і всіх нижчих. Це можна реалізувати математично, але значно простіше скористатися готовою функцією.
.badge {
--isLevelUnlocked: progress(var(--movieCount), 0, var(--badgeLevel));
}
Розберемо цей приклад детальніше.
Розбір прикладу
На сторінці є список порожніх текстових полів, куди користувачі вписують назви фільмів, а також самі бейджі, які вони можуть здобути.
Movie 1 Movie 1
Рівні бейджів задаються через властивість --badgeLevel.
Підрахунок кількості
Якби додані на сторінку елементи були сусідніми (siblings) з тими, які потрібно показати, можна було би скористатися функцією sibling-count() для підрахунку кількості вписаних фільмів. Але в цьому випадку такий підхід не працює, тож підрахунок виконується через JavaScript.
const MOVIES = Array.from(document.querySelectorAll('input[type="text"]'));
document.getElementById('movies').addEventListener('change', e =>
document.body.style.setProperty('--movieCount', MOVIES.filter(el => el.value.trim() !== "").length)
);
Прапорець стану
@property --isLevelUnlocked {
syntax: '';
inherits: true;
initial-value: 0;
}
.badge {
--isLevelUnlocked: progress(var(--movieCount), 0, var(--badgeLevel));
}
Прапорець --isLevelUnlocked оформлено як зареєстровану кастомну властивість, оскільки далі його значення перевірятиметься в контейнерному стильовому запиті.
Container style queries allow us to look at the computed value of a custom property on a parent element and conditionally apply styles to its descendants
Master.dev Blog
У progress() кількість фільмів виступає поточним значенням, 0 — нижньою межею, а рівень бейджа — верхньою межею. Головне тут — порівняння кількості фільмів з рівнем бейджа.
Якщо кількість фільмів дорівнює або перевищує рівень бейджа, progress() повертає 1. Це означає, що потрібно показати бейдж відповідного рівня і всі попередні. Ось як це виглядає для бейджа Flick Fanatic.
Flick Fanatic badge has badge level 6. If the user had typed in 6 or more movies, this badge will be shown. --badgeLevel = 6 --isLevelUnlocked = progress(var(--movieCount), 0, 6) If movie count is 2 progress(cv, lb, ub) progress(2, 0, 6) = 0.33333 Flick Fanatic badge will be hidden If movie count is 6 progress(6, 0, 6) = 1 Flick Fanatic badge will be shown If movie count is 10 progress(10, 0, 6) = 1 (clamped) Flick Fanatic badge will be shown
Показ бейджа
Спершу весь контент бейджів прихований, і показується він лише за відповідної кількості фільмів.
.badge > * {
visibility: hidden;
@container style(--isLevelUnlocked: 1) {
visibility: visible;
}
}
Контейнерний стильовий запит перевіряє, чи виконується умова --isLevelUnlocked: 1 (тобто рівень бейджа збігається з кількістю фільмів або є меншим за неї). Якщо так — бейдж показується.
Завдяки progress() не довелося окремо порівнювати кожен рівень чи проходити циклом по всіх бейджах і оновлювати їхні стилі через JavaScript.
Цей підхід можна застосовувати до будь-яких критеріїв, що потребують подібних порівнянь. Підсумовуючи: не лише пропорційне число, а й фіксовані значення 0 та 1, які повертає progress(), можна використовувати — або напряму у властивості (як opacity), або як прапорець у контейнерному стильовому запиті для стилізації елементів залежно від того, у яку з частин діапазону вони потрапляють.
Джерело: Master.dev Blog


