Анімації, трансформації та 3D у Tailwind v4
Анімації, трансформації та 3D у Tailwind v4
Вступ: чому анімації — це не прикраса, а комунікація
Анімації у веб-інтерфейсі — це не «гарні ефекти заради ефектів». Кожна добре спроєктована мікроанімація несе функціональне навантаження:
- Hover-ефект на кнопці підтверджує, що елемент інтерактивний
- Плавна поява модального вікна дає мозку час «зрозуміти», що відбулося
- Анімація завантаження знімає тривогу очікування
- 3D-перекид картки розкриває прихований вміст із просторовим контекстом
Поганий дизайн анімацій — це анімації заради анімацій: занадто повільні, занадто складні, або такі, що відволікають. Хороший дизайн — непомітний, але відчутний. Ви не думаєте «о, яка гарна анімація» — ви просто почуваєтеся комфортно, користуючись інтерфейсом.
У цій статті ми розберемо всю систему анімацій та трансформацій Tailwind v4: від базових transition до нових 3D-трансформацій, що з'явилися у четвертій версії.
hover:, focus: та інші варіанти) детально розглянута у статті 06. Тут ми зосереджуємося на анімаційній та трансформаційній складовій, не повторюючи варіантний синтаксис.Частина І. Transitions: плавність у кожній взаємодії
1.1. Що таке CSS Transition і як Tailwind його реалізує
CSS Transition — механізм плавної зміни CSS-властивостей з часом. Без transition зміна стилю миттєва. З transition — відбувається за вказану тривалість за вказаною функцією плавності.
Базова модель:
transition: {властивість} {тривалість} {функція-плавності} {затримка}
Tailwind розбиває це на окремі утиліти:
| Група | Утиліта | CSS |
|---|---|---|
| Що | transition, transition-colors, transition-transform, transition-opacity, transition-shadow, transition-all, transition-none | transition-property |
| Скільки | duration-75, duration-100, ..., duration-1000 | transition-duration |
| Як | ease-linear, ease-in, ease-out, ease-in-out | transition-timing-function |
| Коли | delay-0, delay-75, ..., delay-1000 | transition-delay |
1.2. Клас transition: що він робить насправді
Клас transition — не просто «увімкнути переходи». Він задає конкретний список властивостей, що будуть анімуватися:
/* Що генерує клас transition */
.transition {
transition-property: color, background-color, border-color,
text-decoration-color, fill, stroke,
opacity, box-shadow, transform, filter,
backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
Це не transition: all — це ретельно відібраний набір властивостей, що охоплює 99% UI-потреб і при цьому не навантажує GPU зайвими обчисленнями.
Порівняємо всі transition-* класи:
<!-- transition — кольори + тіні + трансформації (рекомендований) -->
<button class="transition hover:bg-indigo-700 hover:shadow-lg hover:scale-105">
<!-- transition-colors — ТІЛЬКИ кольорові властивості -->
<button class="transition-colors hover:bg-indigo-700 hover:text-white">
<!-- Корисно: hover:scale-105 НЕ буде анімуватися — немає зайвих обчислень -->
<!-- transition-transform — ТІЛЬКИ transform -->
<div class="transition-transform hover:scale-110 hover:rotate-3">
<!-- hover:bg-* НЕ буде анімуватися -->
<!-- transition-opacity — ТІЛЬКИ opacity -->
<div class="transition-opacity hover:opacity-50">
<!-- transition-shadow — ТІЛЬКИ box-shadow -->
<div class="transition-shadow hover:shadow-2xl">
<!-- transition-all — ВСЕ (обережно! дорого для GPU) -->
<div class="transition-all hover:bg-red-500 hover:scale-110 hover:rounded-full">
<!-- transition-none — вимкнути transition (перевизначити батьківський) -->
<button class="transition-none">
transition-all анімує кожну властивість, що змінилася. Браузер вимушений перевіряти всі властивості на кожному кадрі — це дорого. Для складних компонентів використовуйте transition (вибіркові властивості) або спеціалізовані класи (transition-colors, transition-transform).1.3. Duration та Timing Function: швидкість і характер
Тривалість (duration-*) визначає, скільки часу займає перехід:
<!-- Шкала duration: 75ms, 100ms, 150ms, 200ms, 300ms, 500ms, 700ms, 1000ms -->
<button class="transition duration-75"> <!-- блискавично — 75ms -->
<button class="transition duration-150"> <!-- стандарт — 150ms (default) -->
<button class="transition duration-300"> <!-- плавно — 300ms -->
<button class="transition duration-700"> <!-- повільно — 700ms -->
<button class="transition duration-1000"><!-- дуже повільно — 1s -->
<!-- Довільна тривалість -->
<button class="transition duration-[350ms]">
Функції плавності (ease-*) визначають характер руху:
<!-- ease-linear: рівномірно (механічний) -->
<div class="transition ease-linear duration-300 hover:translate-x-4">
<!-- ease-in: повільний старт, швидкий фінал (елемент "вилітає") -->
<div class="transition ease-in duration-300 hover:opacity-0">
<!-- ease-out: швидкий старт, повільний фінал (елемент "прилітає") -->
<div class="transition ease-out duration-300 hover:translate-y-0">
<!-- ease-in-out: повільний старт і фінал (природний) — DEFAULT -->
<div class="transition ease-in-out duration-300 hover:scale-105">
- Елементи, що з'являються →
ease-out(швидко з'являється, плавно зупиняється) - Елементи, що зникають →
ease-in(починає повільно, прискорюється і зникає) - Загальні UI-зміни →
ease-in-out(природне відчуття) - Прогрес-бари, лоадери →
ease-linear(рівномірний рух)
Кастомні timing functions через довільні значення:
<!-- Spring-ефект через cubic-bezier -->
<div class="transition ease-[cubic-bezier(0.34,1.56,0.64,1)] duration-500 hover:scale-110">
<!-- cubic-bezier(0.34,1.56,0.64,1) = "spring" — значення > 1 дає "перестрибок" -->
<!-- Більш різкий вхід -->
<div class="transition ease-[cubic-bezier(0.25,0.46,0.45,0.94)] duration-300">
1.4. Практичні патерни transitions
Живий приклад — колекція типових UI-transitions:
<div class="p-6 bg-slate-50 space-y-6" style="font-family: system-ui, sans-serif;">
<!-- === Кнопки === -->
<div>
<p class="text-xs font-bold uppercase tracking-widest text-slate-400 mb-3">Кнопки</p>
<div class="flex flex-wrap gap-3">
<!-- Колір + тінь -->
<button class="px-5 py-2.5 bg-indigo-600 text-white font-semibold rounded-xl text-sm
transition-all duration-200
hover:bg-indigo-700 hover:shadow-lg hover:shadow-indigo-500/30
active:scale-95">
Hover + Shadow
</button>
<!-- Scale + колір -->
<button class="px-5 py-2.5 bg-emerald-500 text-white font-semibold rounded-xl text-sm
transition duration-200
hover:scale-105 hover:bg-emerald-600
active:scale-95">
Scale Up
</button>
<!-- Translate вверх -->
<button class="px-5 py-2.5 bg-violet-600 text-white font-semibold rounded-xl text-sm
transition-[transform,box-shadow] duration-200
hover:-translate-y-1 hover:shadow-xl hover:shadow-violet-500/30
active:translate-y-0">
Lift Effect
</button>
<!-- Outline кнопка → заповнена -->
<button class="px-5 py-2.5 border-2 border-rose-500 text-rose-600 font-semibold rounded-xl text-sm
transition-all duration-200
hover:bg-rose-500 hover:text-white
active:scale-95">
Fill on Hover
</button>
</div>
</div>
<!-- === Картки === -->
<div>
<p class="text-xs font-bold uppercase tracking-widest text-slate-400 mb-3">Картки</p>
<div class="grid grid-cols-3 gap-3">
<div class="bg-white rounded-xl p-4 border border-slate-100 shadow-sm
transition-all duration-300
hover:shadow-xl hover:-translate-y-1 hover:border-indigo-100
cursor-pointer group">
<div class="size-8 bg-indigo-100 rounded-lg flex items-center justify-center mb-3
transition-transform duration-300 group-hover:scale-110">
🚀
</div>
<p class="text-sm font-bold text-slate-800">Lift Card</p>
<p class="text-xs text-slate-400 mt-1">Підіймається при hover</p>
</div>
<div class="bg-white rounded-xl p-4 border border-slate-200
transition-all duration-200
hover:border-emerald-400 hover:shadow-[0_0_0_3px_rgba(52,211,153,0.2)]
cursor-pointer">
<div class="size-8 bg-emerald-100 rounded-lg flex items-center justify-center mb-3">
💎
</div>
<p class="text-sm font-bold text-slate-800">Glow Border</p>
<p class="text-xs text-slate-400 mt-1">Ring effect при hover</p>
</div>
<div class="bg-gradient-to-br from-slate-50 to-slate-100 rounded-xl p-4 border border-slate-200
transition-all duration-300
hover:from-indigo-50 hover:to-violet-50 hover:border-indigo-200
cursor-pointer">
<div class="size-8 bg-white rounded-lg flex items-center justify-center mb-3 shadow-sm">
🌈
</div>
<p class="text-sm font-bold text-slate-800">Gradient Shift</p>
<p class="text-xs text-slate-400 mt-1">Фон змінюється плавно</p>
</div>
</div>
</div>
<!-- === Input Focus === -->
<div>
<p class="text-xs font-bold uppercase tracking-widest text-slate-400 mb-3">Form Focus States</p>
<div class="space-y-3 max-w-xs">
<input type="text" placeholder="Стандартний input"
class="w-full px-4 py-2.5 border-2 border-slate-200 rounded-xl text-sm
transition-all duration-200
focus:border-indigo-500 focus:shadow-[0_0_0_4px_rgba(99,102,241,0.15)]
focus:outline-none placeholder:text-slate-300">
<input type="text" placeholder="Animated label effect"
class="w-full px-4 py-2.5 border-2 border-slate-200 rounded-xl text-sm
bg-slate-50 transition-all duration-200
focus:border-violet-500 focus:bg-white focus:shadow-[0_0_0_4px_rgba(139,92,246,0.15)]
focus:outline-none placeholder:text-slate-300">
</div>
</div>
</div>
1.5. Transition з will-change
Для компонентів із частими анімаціями (нескінченні, або що починаються одразу при завантаженні) варто підказати браузеру наперед виділити GPU-ресурси через will-change:
<!-- Підказка браузеру: цей елемент буде трансформуватися -->
<div class="will-change-transform transition-transform duration-300 hover:scale-105">
<!-- Підказка для opacity -->
<div class="will-change-[opacity] transition-opacity duration-500">
<!-- Підказка для кількох властивостей -->
<div class="will-change-[transform,opacity] transition duration-300">
<!-- Скасувати will-change після анімації (через JS або після навантаження) -->
<!-- Tailwind: will-change-auto -->
<div class="will-change-auto">
will-change — інструмент оптимізації, не панацея. Зловживання (наприклад, will-change: transform на кожному елементі) збільшує споживання пам'яті GPU. Застосовуйте тільки до елементів, що дійсно активно анімуються, і знімайте після завершення анімації.Частина ІІ. Transforms: рух, масштаб, поворот
2.1. Як Tailwind компонує трансформації
У Tailwind трансформації реалізовані через CSS Custom Properties, що дозволяє комбінувати різні transform-операції у одному класі transform:
/* Внутрішня реалізація: transform складається з CSS-змінних */
.scale-110 {
--tw-scale-x: 1.1;
--tw-scale-y: 1.1;
transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z)
var(--tw-scale-x) var(--tw-scale-y) var(--tw-scale-z)
translate(var(--tw-translate-x), var(--tw-translate-y));
}
Завдяки цьому можна комбінувати будь-які трансформації в одному класовому рядку без конфліктів:
<!-- Всі трансформації разом — коректно поєднуються -->
<div class="hover:scale-105 hover:rotate-3 hover:-translate-y-2 transition-transform duration-300">
Масштаб + поворот + підйом
</div>
2.2. Scale: масштабування
<!-- Однакове по X та Y -->
<div class="scale-0"> <!-- 0% — невидимий -->
<div class="scale-50"> <!-- 50% -->
<div class="scale-75"> <!-- 75% -->
<div class="scale-90"> <!-- 90% -->
<div class="scale-95"> <!-- 95% — тонке зменшення (active state) -->
<div class="scale-100"> <!-- 100% — нормальний -->
<div class="scale-105"> <!-- 105% — тонке збільшення (hover) -->
<div class="scale-110"> <!-- 110% -->
<div class="scale-125"> <!-- 125% -->
<div class="scale-150"> <!-- 150% -->
<!-- Незалежно по осях -->
<div class="scale-x-50"> <!-- Тільки по горизонталі -->
<div class="scale-y-125"> <!-- Тільки по вертикалі -->
<div class="scale-x-[-1]"><!-- Горизонтальне дзеркало -->
<div class="scale-y-[-1]"><!-- Вертикальне дзеркало (flip) -->
<!-- Довільні значення -->
<div class="scale-[1.02]"> <!-- Дуже тонке збільшення -->
<div class="hover:scale-[1.015] transition-transform duration-200">
Практичне застосування scale:
<!-- Active (натискання) — scale 95 -->
<button class="active:scale-95 transition-transform duration-100">Натисни</button>
<!-- Hover — scale 105 -->
<img class="hover:scale-105 transition-transform duration-300 overflow-hidden rounded-xl">
<!-- Reveal анімація — від 0 до 100 -->
<div class="scale-0 animate-[scale-in_0.3s_ease-out_forwards]">
2.3. Rotate: обертання
<!-- Позитивні значення — за годинниковою стрілкою -->
<div class="rotate-0"> <!-- 0deg -->
<div class="rotate-1"> <!-- 1deg — ледь помітний нахил -->
<div class="rotate-2"> <!-- 2deg -->
<div class="rotate-3"> <!-- 3deg -->
<div class="rotate-6"> <!-- 6deg -->
<div class="rotate-12"> <!-- 12deg -->
<div class="rotate-45"> <!-- 45deg -->
<div class="rotate-90"> <!-- 90deg -->
<div class="rotate-180"> <!-- 180deg — перевернутий -->
<!-- Негативні значення — проти годинникової -->
<div class="-rotate-1">
<div class="-rotate-45">
<div class="-rotate-90">
<!-- Довільні значення -->
<div class="rotate-[33deg]">
<div class="hover:rotate-[360deg] transition-transform duration-700">
<!-- Повне обертання при hover! -->
</div>
Практичні патерни:
<!-- Іконка стрілки при розкритті accordion -->
<svg class="rotate-0 transition-transform duration-200 group-open:rotate-180">
<!-- Декоративний нахил карток -->
<div class="rotate-2 hover:rotate-0 transition-transform duration-300">
<!-- Спіннер через анімацію (у CSS) -->
<div class="animate-spin border-2 border-indigo-600 border-t-transparent
rounded-full size-5">
2.4. Translate: переміщення
<!-- По горизонталі (translate-x) -->
<div class="translate-x-0"> <!-- 0 -->
<div class="translate-x-1"> <!-- 4px (0.25rem) -->
<div class="translate-x-4"> <!-- 16px (1rem) -->
<div class="translate-x-full"> <!-- 100% ширини елемента -->
<div class="translate-x-1/2"> <!-- 50% ширини — для центрування -->
<div class="-translate-x-1"> <!-- -4px -->
<div class="-translate-x-full"><!-- -100% — виїзд за ліву межу -->
<!-- По вертикалі (translate-y) -->
<div class="translate-y-0">
<div class="-translate-y-1"> <!-- -4px — підйом вверх -->
<div class="-translate-y-px"> <!-- -1px — pixel-perfect підйом -->
<div class="translate-y-full"> <!-- 100% висоти — виїзд вниз -->
<!-- Одночасно по обох осях (v4) -->
<div class="translate-x-4 translate-y-2">
<!-- Довільні значення -->
<div class="translate-x-[calc(-50%+1rem)]"> <!-- Точне позиціонування -->
<div class="-translate-y-[3px]"> <!-- Pixel-perfect -->
Типові UI-патерни:
<!-- Toast notification: виїжджає з правого краю -->
<div class="translate-x-full transition-transform duration-300 ease-out"
id="toast">
Повідомлення
</div>
<!-- При появі: js → classList.remove('translate-x-full') -->
<!-- Tooltip: з'являється знизу елемента -->
<div class="opacity-0 translate-y-2 transition-all duration-200
group-hover:opacity-100 group-hover:translate-y-0">
Підказка
</div>
<!-- Hover lift effect -->
<div class="hover:-translate-y-1 hover:shadow-lg transition-all duration-200">
Картка підіймається
</div>
<!-- Абсолютне центрування через translate -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
Точно в центрі батька
</div>
2.5. Skew: нахил (skew)
<!-- По горизонталі -->
<div class="skew-x-0"> <!-- 0deg -->
<div class="skew-x-1"> <!-- 1deg -->
<div class="skew-x-2"> <!-- 2deg -->
<div class="skew-x-3"> <!-- 3deg -->
<div class="skew-x-6"> <!-- 6deg -->
<div class="skew-x-12"> <!-- 12deg -->
<div class="-skew-x-6"> <!-- -6deg — нахил у протилежний бік -->
<!-- По вертикалі -->
<div class="skew-y-3">
<div class="-skew-y-6">
<!-- Довільні значення -->
<div class="skew-x-[15deg]">
Skew рідко використовується для анімацій — частіше для декоративних діагональних розділювачів секцій:
<!-- Діагональний розділювач між секціями -->
<div class="relative h-24 bg-indigo-600 -skew-y-3 -mt-12 -mb-12"></div>
2.6. Transform Origin: точка обертання
За замовчуванням всі трансформації відбуваються відносно центру елемента. Клас origin-* змінює цю точку:
<!-- Стандартна шкала origin -->
<div class="origin-center"> <!-- центр (default) -->
<div class="origin-top"> <!-- верхній центр -->
<div class="origin-top-right"><!-- верхній правий кут -->
<div class="origin-right"> <!-- правий центр -->
<div class="origin-bottom-right">
<div class="origin-bottom">
<div class="origin-bottom-left">
<div class="origin-left">
<div class="origin-top-left">
<!-- Довільна точка -->
<div class="origin-[33%_75%]">
Практичне застосування:
<!-- Розкриття зверху вниз (як dropdown) -->
<div class="scale-y-0 origin-top transition-transform duration-200 group-open:scale-y-100">
Контент dropdown
</div>
<!-- Поворот із нижнього лівого кута (як сторінка книги) -->
<div class="rotate-0 hover:-rotate-12 origin-bottom-left transition-transform duration-300">
<!-- Zoom із лівого краю (camera pan effect) -->
<img class="scale-100 hover:scale-110 origin-left transition-transform duration-700">
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body class="p-6 bg-slate-50" style="font-family: 'Inter', system-ui, sans-serif;">
<div class="max-w-2xl mx-auto bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-6">
<p class="text-xs font-bold text-slate-400 uppercase tracking-widest text-center">Interactive 2D Transform Playground</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-center">
<!-- Controls Panel -->
<div class="space-y-4 bg-slate-50 p-4 rounded-xl border border-slate-100">
<!-- Scale -->
<div class="space-y-1">
<div class="flex justify-between text-xs font-semibold text-slate-600">
<span>Scale (Масштаб)</span>
<span id="scaleVal" class="font-mono">1.0</span>
</div>
<input type="range" min="0.5" max="1.5" step="0.1" value="1.0" id="scaleRange" oninput="applyTransforms()" class="w-full">
</div>
<!-- Rotate -->
<div class="space-y-1">
<div class="flex justify-between text-xs font-semibold text-slate-600">
<span>Rotate (Поворот)</span>
<span id="rotateVal" class="font-mono">0°</span>
</div>
<input type="range" min="-180" max="180" step="5" value="0" id="rotateRange" oninput="applyTransforms()" class="w-full">
</div>
<!-- Translate X -->
<div class="space-y-1">
<div class="flex justify-between text-xs font-semibold text-slate-600">
<span>Translate X (Зміщення X)</span>
<span id="transXVal" class="font-mono">0px</span>
</div>
<input type="range" min="-50" max="50" step="5" value="0" id="transXRange" oninput="applyTransforms()" class="w-full">
</div>
<!-- Translate Y -->
<div class="space-y-1">
<div class="flex justify-between text-xs font-semibold text-slate-600">
<span>Translate Y (Зміщення Y)</span>
<span id="transYVal" class="font-mono">0px</span>
</div>
<input type="range" min="-50" max="50" step="5" value="0" id="transYRange" oninput="applyTransforms()" class="w-full">
</div>
<!-- Skew X -->
<div class="space-y-1">
<div class="flex justify-between text-xs font-semibold text-slate-600">
<span>Skew X (Нахил X)</span>
<span id="skewXVal" class="font-mono">0°</span>
</div>
<input type="range" min="-30" max="30" step="2" value="0" id="skewXRange" oninput="applyTransforms()" class="w-full">
</div>
<!-- Origin -->
<div class="space-y-1">
<label class="block text-xs font-semibold text-slate-600">Origin (Точка обертання)</label>
<select id="originSelect" onchange="applyTransforms()" class="w-full text-xs bg-white border border-slate-200 rounded-lg p-2 focus:outline-none">
<option value="origin-center">origin-center (Центр)</option>
<option value="origin-top-left">origin-top-left (Верхній лівий)</option>
<option value="origin-top-right">origin-top-right (Верхній правий)</option>
<option value="origin-bottom-left">origin-bottom-left (Нижній лівий)</option>
<option value="origin-bottom-right">origin-bottom-right (Нижній правий)</option>
</select>
</div>
</div>
<!-- Visual Target Display -->
<div class="h-64 flex flex-col items-center justify-center border border-slate-100 rounded-xl bg-slate-50/50 p-4 relative overflow-hidden">
<!-- Helper Dot showing origin point -->
<div id="originDot" class="absolute w-2 h-2 bg-red-500 rounded-full z-10" style="left:50%; top:50%; margin-left:-4px; margin-top:-4px;"></div>
<!-- Target Card -->
<div id="transformTarget" class="w-28 h-28 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex flex-col items-center justify-center text-white shadow-lg transition-transform duration-100 ease-out origin-center">
<span class="text-3xl">🚀</span>
<span class="text-[10px] font-bold mt-1 uppercase tracking-wider">Transform</span>
</div>
</div>
</div>
<!-- Class display -->
<div>
<label class="block text-xs font-semibold text-slate-500 uppercase mb-2">Generated Tailwind Classes</label>
<div class="bg-slate-900 text-slate-300 rounded-lg p-3 text-xs font-mono break-all leading-normal shadow-inner" id="classDisplay">
scale-[1] rotate-[0deg] translate-x-[0px] translate-y-[0px] skew-x-[0deg] origin-center
</div>
</div>
</div>
<script>
function applyTransforms() {
const scale = document.getElementById('scaleRange').value;
const rotate = document.getElementById('rotateRange').value;
const transX = document.getElementById('transXRange').value;
const transY = document.getElementById('transYRange').value;
const skewX = document.getElementById('skewXRange').value;
const origin = document.getElementById('originSelect').value;
// Update Labels
document.getElementById('scaleVal').textContent = parseFloat(scale).toFixed(1);
document.getElementById('rotateVal').textContent = rotate + '°';
document.getElementById('transXVal').textContent = transX + 'px';
document.getElementById('transYVal').textContent = transY + 'px';
document.getElementById('skewXVal').textContent = skewX + '°';
// Apply style to target
const target = document.getElementById('transformTarget');
target.className = `w-28 h-28 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex flex-col items-center justify-center text-white shadow-lg transition-transform duration-100 ease-out ${origin}`;
target.style.transform = `scale(${scale}) rotate(${rotate}deg) translate(${transX}px, ${transY}px) skewX(${skewX}deg)`;
// Update Origin Dot Position
const dot = document.getElementById('originDot');
if (origin === 'origin-center') { dot.style.left = '50%'; dot.style.top = '50%'; }
else if (origin === 'origin-top-left') { dot.style.left = 'calc(50% - 56px)'; dot.style.top = 'calc(50% - 56px)'; }
else if (origin === 'origin-top-right') { dot.style.left = 'calc(50% + 56px)'; dot.style.top = 'calc(50% - 56px)'; }
else if (origin === 'origin-bottom-left') { dot.style.left = 'calc(50% - 56px)'; dot.style.top = 'calc(50% + 56px)'; }
else if (origin === 'origin-bottom-right') { dot.style.left = 'calc(50% + 56px)'; dot.style.top = 'calc(50% + 56px)'; }
// Display classes
const generatedClasses = `scale-[${scale}] rotate-[${rotate}deg] translate-x-[${transX}px] translate-y-[${transY}px] skew-x-[${skewX}deg] ${origin}`;
document.getElementById('classDisplay').textContent = generatedClasses;
}
// Initialize
applyTransforms();
</script>
</body>
</html>
Частина ІІІ. Keyframe-анімації: animate-*
3.1. Вбудовані анімації Tailwind
Tailwind постачається з невеликим, але практичним набором вбудованих keyframe-анімацій:
| Клас | Опис | Використання |
|---|---|---|
animate-spin | Нескінченне обертання (360°) | Spinner, лоадер |
animate-ping | Пульсуючий розширюючийся ring | Notification badge, статус «онлайн» |
animate-pulse | Плавне мерехтіння opacity 100%→50% | Skeleton loader |
animate-bounce | Підстрибування вгору-вниз | Scroll-down стрілка, attention-getter |
animate-none | Відключити анімацію | Перевизначення |
<!-- Spinner: обертання -->
<div class="animate-spin size-6 border-2 border-indigo-600 border-t-transparent rounded-full">
</div>
<!-- Ping: пульсуючий badge -->
<span class="relative flex size-3">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full size-3 bg-emerald-500"></span>
</span>
<!-- Pulse: skeleton loader -->
<div class="animate-pulse flex space-x-4">
<div class="rounded-full bg-slate-200 size-10"></div>
<div class="flex-1 space-y-2 py-1">
<div class="h-4 bg-slate-200 rounded w-3/4"></div>
<div class="h-4 bg-slate-200 rounded w-1/2"></div>
</div>
</div>
<!-- Bounce: scroll indicator -->
<div class="animate-bounce flex items-center justify-center size-8 rounded-full bg-white shadow-md">
↓
</div>
Живий приклад всіх вбудованих анімацій:
<div class="p-8 bg-slate-50 flex flex-wrap gap-8 items-start justify-center"
style="font-family: system-ui, sans-serif;">
<!-- Spin -->
<div class="flex flex-col items-center gap-3">
<div class="animate-spin size-10 border-4 border-indigo-200 border-t-indigo-600 rounded-full"></div>
<p class="text-xs font-semibold text-slate-500">animate-spin</p>
<p class="text-xs text-slate-400">Лоадер</p>
</div>
<!-- Ping (notification badge) -->
<div class="flex flex-col items-center gap-3">
<div class="relative">
<div class="size-10 bg-slate-200 rounded-lg flex items-center justify-center text-sm">🔔</div>
<span class="absolute -top-1 -right-1 flex size-3">
<span class="animate-ping absolute inline-flex size-full rounded-full bg-rose-400 opacity-75"></span>
<span class="relative inline-flex rounded-full size-3 bg-rose-500"></span>
</span>
</div>
<p class="text-xs font-semibold text-slate-500">animate-ping</p>
<p class="text-xs text-slate-400">Badge</p>
</div>
<!-- Pulse (skeleton) -->
<div class="flex flex-col items-center gap-3">
<div class="animate-pulse w-32 space-y-2">
<div class="h-3 bg-slate-300 rounded-full w-3/4"></div>
<div class="h-3 bg-slate-300 rounded-full"></div>
<div class="h-3 bg-slate-300 rounded-full w-5/6"></div>
<div class="h-3 bg-slate-200 rounded-full w-1/2"></div>
</div>
<p class="text-xs font-semibold text-slate-500">animate-pulse</p>
<p class="text-xs text-slate-400">Skeleton</p>
</div>
<!-- Bounce -->
<div class="flex flex-col items-center gap-3">
<div class="animate-bounce size-10 bg-indigo-600 rounded-xl flex items-center justify-center text-white font-black">↓</div>
<p class="text-xs font-semibold text-slate-500">animate-bounce</p>
<p class="text-xs text-slate-400">Scroll indicator</p>
</div>
<!-- Комбінація: пульсуюча аватарка онлайн -->
<div class="flex flex-col items-center gap-3">
<div class="relative">
<div class="size-10 bg-gradient-to-br from-violet-500 to-indigo-600 rounded-full flex items-center justify-center text-white font-bold text-sm">AB</div>
<span class="absolute bottom-0 right-0 flex size-3">
<span class="animate-ping absolute inline-flex size-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full size-3 bg-emerald-500 border-2 border-white"></span>
</span>
</div>
<p class="text-xs font-semibold text-slate-500">Комбо: ping</p>
<p class="text-xs text-slate-400">Онлайн-статус</p>
</div>
</div>
3.2. Управління анімацією: duration, delay, iteration, direction
Анімації можна налаштовувати так само, як переходи:
<!-- Тривалість (за замовчуванням: 1s для більшості) -->
<div class="animate-spin duration-[2000ms]"> <!-- повільний спін -->
<div class="animate-spin duration-500"> <!-- швидкий спін -->
<!-- Затримка перед стартом -->
<div class="animate-bounce delay-150">
<div class="animate-bounce delay-300">
<div class="animate-bounce delay-500">
<!-- Ефект: три крапки bouncing з зміщенням (typing indicator) -->
<!-- Пауза/зупинка анімації -->
<div class="animate-spin [animation-play-state:paused] hover:[animation-play-state:running]">
<!-- Запуск анімації тільки при hover -->
</div>
<!-- Одноразова (forwards зберігає фінальний стан) -->
<div class="animate-[fade-in_0.5s_ease-out_forwards]">
<!-- Зворотна анімація -->
<div class="[animation-direction:reverse] animate-bounce">
<!-- Кількість повторень -->
<div class="[animation-iteration-count:3] animate-bounce"> <!-- 3 рази -->
<div class="[animation-iteration-count:infinite] animate-bounce"> <!-- нескінченно -->
Typing indicator (три bouncing крапки з delay):
<div class="p-8 bg-slate-100 flex items-center justify-center gap-6"
style="font-family: system-ui, sans-serif;">
<!-- Typing indicator -->
<div class="bg-white rounded-2xl rounded-bl-sm px-4 py-3 shadow-sm flex items-center gap-1.5">
<div class="size-2 rounded-full bg-slate-400 animate-bounce [animation-delay:0ms]"></div>
<div class="size-2 rounded-full bg-slate-400 animate-bounce [animation-delay:150ms]"></div>
<div class="size-2 rounded-full bg-slate-400 animate-bounce [animation-delay:300ms]"></div>
</div>
<!-- Progress dots -->
<div class="flex gap-2">
<div class="size-2.5 rounded-full bg-indigo-600 animate-bounce [animation-delay:0ms]"></div>
<div class="size-2.5 rounded-full bg-indigo-600 animate-bounce [animation-delay:100ms]"></div>
<div class="size-2.5 rounded-full bg-indigo-600 animate-bounce [animation-delay:200ms]"></div>
<div class="size-2.5 rounded-full bg-indigo-600 animate-bounce [animation-delay:300ms]"></div>
</div>
<!-- Pulse ring (notification) -->
<div class="relative flex items-center justify-center">
<div class="animate-ping absolute size-12 rounded-full bg-indigo-400 opacity-30"></div>
<div class="animate-ping absolute size-8 rounded-full bg-indigo-400 opacity-40 [animation-delay:200ms]"></div>
<div class="relative size-6 rounded-full bg-indigo-600 flex items-center justify-center">
<span class="text-white text-xs font-bold">!</span>
</div>
</div>
</div>
3.3. Кастомні @keyframes у Tailwind v4
У Tailwind v4 кастомні keyframe-анімації визначаються прямо у CSS-файлі — без конфігурації JavaScript:
@import 'tailwindcss';
/* === Визначення keyframes === */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in-down {
from {
opacity: 0;
transform: translateY(-16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.92);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slide-in-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes slide-out-right {
from { transform: translateX(0); }
to { transform: translateX(100%); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 50%, 90% { transform: translateX(-6px); }
30%, 70% { transform: translateX(6px); }
}
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.18); }
100% { transform: scale(1); }
}
/* === Реєстрація у @theme для використання через animate-* === */
@theme {
--animate-fade-in: fade-in 0.4s ease-out;
--animate-fade-in-up: fade-in-up 0.5s ease-out;
--animate-fade-in-down: fade-in-down 0.5s ease-out;
--animate-scale-in: scale-in 0.3s ease-out;
--animate-slide-in-right: slide-in-right 0.35s ease-out;
--animate-slide-out-right:slide-out-right 0.35s ease-in forwards;
--animate-shake: shake 0.5s ease-in-out;
--animate-pop: pop 0.25s ease-in-out;
}
Після реєстрації у @theme класи стають доступними:
<!-- Елемент з'являється знизу -->
<div class="animate-fade-in-up">Контент</div>
<!-- Модальне вікно масштабується при відкритті -->
<div class="animate-scale-in">Модальне вікно</div>
<!-- Toast виїжджає справа -->
<div class="animate-slide-in-right">Повідомлення</div>
<!-- Форма трясеться при помилці -->
<form class="animate-shake" id="login-form">
<!-- Лайк «pop» -->
<button onclick="this.classList.add('animate-pop')"
onanimationend="this.classList.remove('animate-pop')">
❤️
</button>
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.92); }
to { opacity: 1; transform: scale(1); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 50%, 90% { transform: translateX(-6px); }
30%, 70% { transform: translateX(6px); }
}
@keyframes pop {
0% { transform: scale(1); }
50% { transform: scale(1.18); }
100% { transform: scale(1); }
}
.animate-fade-in-up { animation: fadeInUp 0.5s ease-out forwards; }
.animate-scale-in { animation: scaleIn 0.3s ease-out forwards; }
.animate-shake { animation: shake 0.5s ease-in-out forwards; }
.animate-pop { animation: pop 0.25s ease-in-out forwards; }
</style>
</head>
<body class="p-6 bg-slate-50" style="font-family: 'Inter', system-ui, sans-serif;">
<div class="max-w-md mx-auto bg-white rounded-2xl border border-slate-200 p-6 shadow-sm space-y-6">
<p class="text-xs font-bold text-slate-400 uppercase tracking-widest text-center">Custom Keyframe Animations Trigger</p>
<!-- Target Animation Box -->
<div class="h-44 flex items-center justify-center border border-slate-100 bg-slate-50/50 rounded-xl p-4 overflow-hidden">
<div id="animateTarget" class="w-32 h-32 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex flex-col items-center justify-center text-white shadow-lg">
<span id="targetIcon" class="text-3xl">🔮</span>
<span id="targetLabel" class="text-[10px] font-bold mt-1 uppercase tracking-wider">Animation</span>
</div>
</div>
<!-- Buttons Panel -->
<div class="grid grid-cols-2 gap-3">
<button onclick="triggerAnim('animate-fade-in-up', '🔮', 'Fade In Up')" class="px-3 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition-all">
✨ Fade In Up
</button>
<button onclick="triggerAnim('animate-scale-in', '📦', 'Scale In')" class="px-3 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition-all">
🔎 Scale In
</button>
<button onclick="triggerAnim('animate-shake', '⚠', 'Shake')" class="px-3 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition-all">
⚡ Shake
</button>
<button onclick="triggerAnim('animate-pop', '💖', 'Pop')" class="px-3 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs font-semibold rounded-lg transition-all">
❤️ Pop (Like)
</button>
</div>
</div>
<script>
function triggerAnim(cls, icon, label) {
const target = document.getElementById('animateTarget');
const targetIcon = document.getElementById('targetIcon');
const targetLabel = document.getElementById('targetLabel');
// Set details
targetIcon.textContent = icon;
targetLabel.textContent = label;
// Clear current animations
target.className = "w-32 h-32 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl flex flex-col items-center justify-center text-white shadow-lg";
// Force reflow
void target.offsetWidth;
// Add class
target.classList.add(cls);
}
// Listen to animation end and clean class
const target = document.getElementById('animateTarget');
target.addEventListener('animationend', (e) => {
target.classList.remove(e.animationName);
});
</script>
</body>
</html>
Довільні анімації без реєстрації — через синтаксис animate-[...]:
<!-- Пряме використання без @theme -->
<div class="animate-[fade-in-up_0.4s_ease-out]">
<div class="animate-[slide-in-right_0.3s_ease-out_forwards]">
<!-- Тільки один раз (forwards) -->
<div class="animate-[scale-in_0.3s_ease-out_forwards]">
Частина IV. 3D-трансформації: нові можливості v4
4.1. Що нового у v4: повна підтримка 3D
Tailwind v4 додав повноцінну підтримку 3D-трансформацій — те, що раніше вимагало написати [style="transform: rotateX(45deg)"] або окремий CSS-клас, тепер реалізується через стандартні утиліти.
Новий набір 3D-утиліт:
| Утиліта | CSS | Опис |
|---|---|---|
perspective-{n} | perspective: {n}px | Глибина перспективи |
perspective-origin-* | perspective-origin | Точка зору |
rotate-x-{deg} | rotateX({deg}deg) | Поворот по осі X |
rotate-y-{deg} | rotateY({deg}deg) | Поворот по осі Y |
rotate-z-{deg} | rotateZ({deg}deg) | Поворот по осі Z (= rotate-*) |
scale-z-{n} | scaleZ({n}) | Масштаб по осі Z |
translate-z-{n} | translateZ({n}) | Переміщення по осі Z |
transform-style-3d | transform-style: preserve-3d | Зберегти 3D-простір для нащадків |
transform-style-flat | transform-style: flat | Скасувати 3D для нащадків |
backface-visible | backface-visibility: visible | Зворотній бік видимий |
backface-hidden | backface-visibility: hidden | Зворотній бік прихований |
4.2. Perspective: налаштування глибини
perspective — ключ до реалістичного 3D. Він визначає відстань від спостерігача до площини Z=0. Чим менше значення — тим «ближчий» спостерігач, тим більш драматична перспектива:
<!-- perspective застосовується до БАТЬКА, а трансформація — до НАЩАДКА -->
<div class="perspective-[500px]"> <!-- близька перспектива — драматична -->
<div class="rotate-y-45">Картка</div>
</div>
<div class="perspective-[800px]"> <!-- середня — збалансована -->
<div class="rotate-y-45">Картка</div>
</div>
<div class="perspective-[1200px]"> <!-- далека — тонка -->
<div class="rotate-y-45">Картка</div>
</div>
<!-- Вбудована шкала perspective -->
<div class="perspective-dramatic"> <!-- 100px — дуже близько -->
<div class="perspective-near"> <!-- 300px -->
<div class="perspective-normal"> <!-- 500px -->
<div class="perspective-midrange"> <!-- 800px -->
<div class="perspective-distant"> <!-- 1200px -->
4.3. Класичний 3D card flip
Card flip (перекидання картки) — найпоширеніший 3D-ефект в UI. Реалізується через rotate-y-180 та backface-hidden:
<!-- Обгортка: встановлює perspective та відстежує hover -->
<div class="perspective-[800px] group w-64 h-40 cursor-pointer">
<!-- Внутрішній контейнер: обертається при hover -->
<!-- transform-style-3d: дочірні елементи живуть у 3D-просторі -->
<div class="relative w-full h-full transition-transform duration-700 ease-in-out
transform-style-3d group-hover:rotate-y-180">
<!-- Лицьова сторона -->
<div class="absolute inset-0 rounded-2xl bg-indigo-600 text-white
flex items-center justify-center font-bold text-lg
backface-hidden">
Лицьова
</div>
<!-- Зворотня сторона: вже повернута на 180° -->
<div class="absolute inset-0 rounded-2xl bg-violet-600 text-white
flex items-center justify-center font-bold text-lg
backface-hidden rotate-y-180">
Зворотня
</div>
</div>
</div>
Живий приклад з кількома варіантами:
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<script src="https://cdn.tailwindcss.com/4.0"></script>
</head>
<body class="bg-slate-100 p-8 flex flex-wrap gap-8 justify-center items-start"
style="font-family: system-ui, sans-serif; min-height: 100vh;">
<p class="w-full text-center text-xs font-bold uppercase tracking-widest text-slate-400 mb-2">
Наведіть курсор для 3D-перевороту
</p>
<!-- Card Flip: горизонтальний -->
<div class="flex flex-col items-center gap-2">
<div class="[perspective:800px] group w-44 h-28 cursor-pointer">
<div class="relative w-full h-full [transition:transform_0.6s_ease-in-out]
[transform-style:preserve-3d] group-hover:[transform:rotateY(180deg)]">
<!-- Front -->
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-indigo-500 to-violet-600
flex flex-col items-center justify-center text-white
[backface-visibility:hidden]">
<p class="text-2xl mb-1">📦</p>
<p class="text-sm font-bold">Замовлення</p>
<p class="text-xs opacity-70">Hover для деталей</p>
</div>
<!-- Back -->
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-violet-600 to-fuchsia-600
flex flex-col items-center justify-center text-white p-4
[backface-visibility:hidden] [transform:rotateY(180deg)]">
<p class="text-xs font-bold mb-1">Деталі замовлення</p>
<p class="text-xs opacity-80">ID: #4821</p>
<p class="text-xs opacity-80">Статус: В дорозі</p>
<p class="text-xs opacity-80">ETA: 2 дні</p>
</div>
</div>
</div>
<p class="text-xs text-slate-500">rotateY (горизонт.)</p>
</div>
<!-- Card Flip: вертикальний -->
<div class="flex flex-col items-center gap-2">
<div class="[perspective:800px] group w-44 h-28 cursor-pointer">
<div class="relative w-full h-full [transition:transform_0.6s_ease-in-out]
[transform-style:preserve-3d] group-hover:[transform:rotateX(180deg)]">
<!-- Front -->
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-emerald-500 to-teal-600
flex flex-col items-center justify-center text-white
[backface-visibility:hidden]">
<p class="text-2xl mb-1">💳</p>
<p class="text-sm font-bold">Картка</p>
<p class="text-xs opacity-70">**** 4242</p>
</div>
<!-- Back -->
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-teal-600 to-cyan-700
flex flex-col items-center justify-center text-white p-4
[backface-visibility:hidden] [transform:rotateX(180deg)]">
<div class="w-full h-6 bg-black/30 rounded mb-3"></div>
<p class="text-xs font-bold">CVV: ***</p>
<p class="text-xs opacity-70 mt-1">Термін: 12/28</p>
</div>
</div>
</div>
<p class="text-xs text-slate-500">rotateX (вертик.)</p>
</div>
<!-- Cube: 3D куб -->
<div class="flex flex-col items-center gap-2">
<div class="[perspective:400px] w-20 h-20 cursor-pointer group">
<div class="relative w-full h-full [transform-style:preserve-3d]
[transition:transform_1s_ease-in-out]
group-hover:[transform:rotateY(90deg)_rotateX(20deg)]">
<!-- Верхня грань -->
<div class="absolute inset-0 [transform:rotateX(90deg)_translateZ(40px)]
bg-indigo-400/80 rounded-sm border border-indigo-300/50
flex items-center justify-center text-white text-xs font-bold">
Верх
</div>
<!-- Передня грань -->
<div class="absolute inset-0 [transform:translateZ(40px)]
bg-indigo-600 rounded-sm border border-indigo-500
flex items-center justify-center text-white font-black text-lg">
Tw
</div>
<!-- Права грань -->
<div class="absolute inset-0 [transform:rotateY(90deg)_translateZ(40px)]
bg-violet-600/80 rounded-sm border border-violet-500/50
flex items-center justify-center text-white text-xs font-bold">
v4
</div>
</div>
</div>
<p class="text-xs text-slate-500">3D Cube</p>
</div>
<!-- Rotate X: нахил до глядача -->
<div class="flex flex-col items-center gap-2">
<div class="[perspective:600px] cursor-pointer group">
<div class="w-44 h-28 rounded-2xl bg-gradient-to-br from-rose-500 to-orange-500
flex items-center justify-center text-white font-bold
[transition:transform_0.4s_ease-out]
group-hover:[transform:rotateX(-15deg)]
shadow-lg group-hover:shadow-2xl group-hover:shadow-rose-500/40">
<p class="text-sm">Нахил до глядача</p>
</div>
</div>
<p class="text-xs text-slate-500">rotateX hover tilt</p>
</div>
</body>
</html>
4.4. Паралакс і тилт-ефект через JavaScript
Тилт-ефект (картка нахиляється за курсором) поєднує 3D-трансформації з JavaScript:
<!-- Картка з тилт-ефектом -->
<div id="tilt-card"
class="[perspective:600px] w-64 h-40 cursor-pointer"
onmousemove="handleTilt(event, this)"
onmouseleave="resetTilt(this)">
<div id="tilt-inner"
class="w-full h-full rounded-2xl bg-gradient-to-br from-indigo-600 to-violet-700
text-white flex items-center justify-center font-bold text-xl
shadow-xl [transform-style:preserve-3d]
transition-transform duration-100">
<span class="[transform:translateZ(30px)]">Tailwind v4</span>
</div>
</div>
<script>
function handleTilt(e, container) {
const inner = container.querySelector('#tilt-inner') ?? container.firstElementChild
const rect = container.getBoundingClientRect()
const x = (e.clientX - rect.left) / rect.width - 0.5 // від -0.5 до 0.5
const y = (e.clientY - rect.top) / rect.height - 0.5
const rotateX = (-y * 20).toFixed(1) // від -10 до 10 deg
const rotateY = ( x * 20).toFixed(1)
inner.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
}
function resetTilt(container) {
const inner = container.querySelector('#tilt-inner') ?? container.firstElementChild
inner.style.transform = 'rotateX(0deg) rotateY(0deg)'
}
</script>
4.5. Анімація доступності: prefers-reduced-motion
Не всі користувачі комфортно ставляться до анімацій. Люди з вестибулярними порушеннями, епілепсією або просто налаштованою системою «зменшити рух» — всі вони потребують поваги до своїх уподобань.
Tailwind надає варіант motion-reduce: та motion-safe::
<!-- motion-safe: — анімація ТІЛЬКИ якщо користувач НЕ вимкнув рух -->
<div class="motion-safe:animate-bounce">
<!-- motion-reduce: — альтернатива для тих, хто вимкнув -->
<div class="motion-reduce:transition-none motion-reduce:transform-none
transition duration-300 hover:scale-105">
<!-- Spinner: повільніший для motion-reduce -->
<div class="animate-spin motion-reduce:animate-[spin_3s_linear_infinite]">
Правильна стратегія для критичних анімацій:
<!-- За замовчуванням — статично.
Анімація тільки якщо рух прийнятний. -->
<div class="opacity-100
motion-safe:opacity-0
motion-safe:translate-y-4
motion-safe:animate-fade-in-up">
Контент
</div>
<!-- Тест: відкрийте DevTools → Rendering → Emulate CSS media feature → prefers-reduced-motion: reduce -->
motion-reduce:animate-none недостатньо — він зупиняє анімацію, але залишає початковий стан (наприклад, opacity-0). Завжди давайте motion-reduce:-стан видимий та функціональний без анімації.Частина V. Завдання для самоперевірки
Завдання 1.1. Аналіз коду.
Поясніть, що відбудеться при hover, і чому:
<!-- Фрагмент A -->
<button class="bg-blue-500 transition-colors duration-300 hover:bg-blue-700 hover:scale-105">
<!-- Фрагмент B -->
<button class="bg-blue-500 transition duration-300 hover:bg-blue-700 hover:scale-105">
<!-- Фрагмент C -->
<button class="bg-blue-500 transition-all duration-300 hover:bg-blue-700 hover:scale-105 hover:rounded-full">
Яка між ними різниця? Який варіант оптимальніший для GPU і чому?
Завдання 1.2. Центрування через translate.
Як правильно відцентрувати position: absolute елемент точно по центру батьківського position: relative блоку без знання точних розмірів елемента? Напишіть HTML + Tailwind-класи.
Завдання 1.3. Origin та Rotation.
Реалізуйте наступні ефекти:
- Картка, що нахиляється на 8° вправо при hover — з поворотом від нижнього лівого кута
- Іконка шеврона (
›), що при hover повертається на 90° (стрілка "вниз" → стрілка "вправо") — анімація черезtransition-transform duration-200 - Кнопка "лайк" з
animate-pop— спочатку стискається до 95%, потім збільшується до 115%, потім повертається до 100%
Завдання 2.1. Анімований hero section.
Реалізуйте hero section із послідовною появою елементів:
- Заголовок:
fade-in-downзdelay-0 - Підзаголовок:
fade-in-upзdelay-200 - Кнопки:
fade-in-upзdelay-400 - Фонова картинка/градієнт:
fade-inзdelay-0(інша анімація)
Кожна анімація — одноразова (forwards). Після завершення елементи залишаються видимими.
Завдання 2.2. Toast notification system.
Реалізуйте систему сповіщень:
- Toast виїжджає справа через
animate-slide-in-right - Через 3 секунди: виїжджає назад через
animate-slide-out-right - Toast помилки: трясеться через
animate-shake - Стек: кожен наступний toast з'являється нижче попереднього
Технічно: JavaScript + CSS-класи. Tailwind не потребує модифікації — тільки стандартні утиліти + кастомні keyframes.
Завдання 2.3. Картка з тилт-ефектом.
Реалізуйте картку профілю з:
- 3D тилт-ефектом при русі курсора (як у розділі 4.4)
transform-style-3d+perspective-[600px]- Елемент з
[transform:translateZ(30px)]— аватар «виступає» з картки - При
prefers-reduced-motion: reduce— тилт-ефект відсутній, картка статична
Завдання 3.1. Анімаційна бібліотека через @theme.
Побудуйте власну бібліотеку анімацій у @theme, що охоплює всі типові потреби production-застосунку:
Поява елементів (enter):
animate-enter-fade— тільки opacityanimate-enter-up— знизу з opacityanimate-enter-down— зверху з opacityanimate-enter-scale— від 90% scale з opacityanimate-enter-slide-right— виїзд справа
Зникнення елементів (leave):
animate-leave-fadeanimate-leave-upanimate-leave-scaleanimate-leave-slide-right
Увага (attention):
animate-shake— для форм із помилкамиanimate-pop— для like/heartanimate-wiggle— для notification bell
Нескінченні:
animate-float— плавне гойдання (для hero illustrations)animate-pulse-glow— пульсуюче glow (для CTA-кнопок)
Для кожної напишіть @keyframes та зареєструйте в @theme. Задокументуйте у коментарях: назву, опис, типовий випадок застосування.
Завдання 3.2. Мікроанімації для форми.
Побудуйте форму з повним набором мікроанімацій:
- Label: піднімається вгору при focus (floating label)
- Input border: плавна зміна кольору + glow-ring при focus
- Validation: checkmark з'являється через
scale-inпри правильному значенні; помилка —shake+ червона рамка - Submit кнопка: при кліку —
scale-95, при завантаженні — спіннер (animate-spin) замість тексту, при успіху —animate-pop+ зелений колір - Успіх: весь form виїжджає вгору (
animate-leave-up), з'являється success message (animate-enter-scale)
Усі анімації мають бути вимкнені при prefers-reduced-motion: reduce.
Підсумок
Ця стаття охопила повну систему анімацій та трансформацій Tailwind v4:
Transitions
transition → конкретний список властивостей. duration-* → тривалість. ease-* → характер. delay-* → затримка. Уникайте transition-all — воно дорого.Transforms
scale-*, rotate-*, translate-*, skew-* поєднуються без конфліктів через CSS Custom Properties. origin-* контролює точку трансформації.Keyframes
spin, ping, pulse, bounce. Кастомні: @keyframes + реєстрація в @theme. Довільні: animate-[name_duration_easing_fill].3D у v4
perspective-* на батьку + rotate-x/y-* на нащадку + transform-style-3d + backface-hidden = card flip, cube, tilt. Нова нативна підтримка без хаків.Доступність
motion-safe: та motion-reduce: — обов'язкові для production. Завжди тестуйте з prefers-reduced-motion: reduce увімкненим.Попередня стаття: Довільні значення та контейнерні запитиНаступна стаття: Tailwind CLI, PostCSS та інтеграція з фреймворками
Довільні значення та контейнерні запити у Tailwind v4
Вичерпний посібник з довільних значень у Tailwind v4: синтаксис квадратних дужок [], нові круглі дужки (--var), довільні властивості, calc() та CSS-функції. Контейнерні запити @container: вбудована підтримка v4, варіанти @min-*, @max-*, компонентно-орієнтована адаптивність.
Tailwind CLI, PostCSS та інтеграція з фреймворками
Вичерпний посібник із налаштування Tailwind CSS v4: Tailwind CLI, інтеграція з Vite, Next.js, Nuxt. PostCSS-конфігурація. CSS-entry point замість tailwind.config.js. Оптимізація збірки, purge та production-build. IntelliSense та інструменти розробника.