CSS @keyframes + animation — декларативный способ анимировать. Web Animations API (WAAPI) — его JavaScript-аналог: те же возможности, но с программным управлением: пауза, перемотка, реверс, динамические параметры.
const element = document.querySelector('.box')
// Создаёт анимацию и сразу запускает её
const animation = element.animate(
// Keyframes — массив или объект
[
{ transform: 'translateX(0)', opacity: 1 },
{ transform: 'translateX(200px)', opacity: 0.5 },
{ transform: 'translateX(400px)', opacity: 1 },
],
// Timing options
{
duration: 1000, // мс
iterations: 2, // Количество повторений (Infinity — бесконечно)
direction: 'alternate', // normal | reverse | alternate | alternate-reverse
easing: 'ease-in-out', // или кривая Безье: 'cubic-bezier(0.4, 0, 0.2, 1)'
fill: 'forwards', // none | forwards | backwards | both
delay: 200, // Задержка перед стартом
}
)animation.pause() // Пауза
animation.play() // Запуск/продолжение
animation.reverse() // Реверс (меняет направление)
animation.cancel() // Отменить и вернуть в исходное состояние
animation.finish() // Перемотать в конец
// Прогресс
animation.currentTime = 500 // Установить позицию в мс
animation.playbackRate = 2 // Скорость (2 = вдвое быстрее, -1 = назад)
// Состояние
console.log(animation.playState) // 'idle' | 'running' | 'paused' | 'finished'
console.log(animation.currentTime) // текущее время в мс
console.log(animation.effect.getTiming().duration) // длительностьanimation.addEventListener('finish', () => {
console.log('Анимация завершена')
})
animation.addEventListener('cancel', () => {
console.log('Анимация отменена')
})
// Promise-based API
animation.finished.then(() => {
console.log('Можно делать следующее действие')
})
animation.ready.then(() => {
console.log('Анимация готова к воспроизведению')
})// Создаём эффект отдельно от анимации
const effect = new KeyframeEffect(
element,
[
{ transform: 'scale(1)', offset: 0 }, // offset: 0–1 (как % в @keyframes)
{ transform: 'scale(1.2)', offset: 0.5 },
{ transform: 'scale(1)', offset: 1 },
],
{ duration: 600, easing: 'ease' }
)
const animation = new Animation(effect, document.timeline)
animation.play()Анимировать безопасно (через compositing, не вызывает layout):
transform (translate, scale, rotate)opacityfilterАнимировать дорого (вызывает layout/paint):
width, height, top, leftmargin, paddingfont-size// Правильно — анимируем transform
element.animate([
{ transform: 'translateY(0)' },
{ transform: 'translateY(-20px)' },
], { duration: 300, fill: 'forwards' })
// Дорого — анимируем height
element.animate([
{ height: '0px' },
{ height: '200px' },
], { duration: 300 }) // Вызывает layout на каждом кадре| CSS @keyframes | Web Animations API |
|-----------------------------|----------------------------------|
| Декларативно в CSS | Программно в JS |
| Нет pause/play из JS | Полное управление |
| Статические параметры | Динамические: duration, easing |
| animationend событие | Promise animation.finished |
| Нет доступа к прогрессу | currentTime / playbackRate |
Контроллер анимации через Web Animations API с play, pause, reverse и отображением прогресса
// Web Animations API — полный контроль над анимацией
const box = document.createElement('div')
box.style.cssText = `
width: 60px; height: 60px;
background: #7b2ff7;
border-radius: 8px;
margin: 20px;
`
document.body.appendChild(box)
// Создаём анимацию через element.animate()
const animation = box.animate(
[
{ transform: 'translateX(0px) rotate(0deg)', background: '#7b2ff7' },
{ transform: 'translateX(200px) rotate(180deg)', background: '#06b6d4' },
{ transform: 'translateX(0px) rotate(360deg)', background: '#7b2ff7' },
],
{
duration: 2000,
iterations: Infinity,
easing: 'ease-in-out',
}
)
// Панель управления
const controls = document.createElement('div')
controls.style.cssText = 'display: flex; gap: 8px; margin: 8px 20px; flex-wrap: wrap;'
document.body.appendChild(controls)
function makeBtn(label, onClick) {
const btn = document.createElement('button')
btn.textContent = label
btn.style.cssText = 'padding: 6px 12px; border-radius: 4px; border: 1px solid #7b2ff7; background: white; cursor: pointer; font-size: 13px;'
btn.onclick = onClick
controls.appendChild(btn)
return btn
}
makeBtn('Пауза', () => { animation.pause(); logState() })
makeBtn('Играть', () => { animation.play(); logState() })
makeBtn('Реверс', () => { animation.reverse(); logState() })
makeBtn('2x скорость', () => { animation.playbackRate = 2; logState() })
makeBtn('0.5x скорость', () => { animation.playbackRate = 0.5; logState() })
makeBtn('Перемотать на 50%', () => {
const timing = animation.effect.getTiming()
animation.currentTime = timing.duration * 0.5
logState()
})
const stateEl = document.createElement('pre')
stateEl.style.cssText = 'margin: 8px 20px; font-size: 12px; background: #1a202c; color: #a0aec0; padding: 8px; border-radius: 4px;'
document.body.appendChild(stateEl)
function logState() {
const timing = animation.effect.getTiming()
const progress = animation.currentTime !== null
? ((animation.currentTime % timing.duration) / timing.duration * 100).toFixed(1)
: 0
stateEl.textContent = [
`playState: ${animation.playState}`,
`currentTime: ${Math.round(animation.currentTime ?? 0)}ms`,
`progress: ${progress}%`,
`playbackRate: ${animation.playbackRate}x`,
`duration: ${timing.duration}ms`,
].join('\n')
}
// Обновляем состояние каждые 100ms
setInterval(logState, 100)
logState()
console.log('animation.playState:', animation.playState) // running
console.log('Используй кнопки для управления анимацией')CSS @keyframes + animation — декларативный способ анимировать. Web Animations API (WAAPI) — его JavaScript-аналог: те же возможности, но с программным управлением: пауза, перемотка, реверс, динамические параметры.
const element = document.querySelector('.box')
// Создаёт анимацию и сразу запускает её
const animation = element.animate(
// Keyframes — массив или объект
[
{ transform: 'translateX(0)', opacity: 1 },
{ transform: 'translateX(200px)', opacity: 0.5 },
{ transform: 'translateX(400px)', opacity: 1 },
],
// Timing options
{
duration: 1000, // мс
iterations: 2, // Количество повторений (Infinity — бесконечно)
direction: 'alternate', // normal | reverse | alternate | alternate-reverse
easing: 'ease-in-out', // или кривая Безье: 'cubic-bezier(0.4, 0, 0.2, 1)'
fill: 'forwards', // none | forwards | backwards | both
delay: 200, // Задержка перед стартом
}
)animation.pause() // Пауза
animation.play() // Запуск/продолжение
animation.reverse() // Реверс (меняет направление)
animation.cancel() // Отменить и вернуть в исходное состояние
animation.finish() // Перемотать в конец
// Прогресс
animation.currentTime = 500 // Установить позицию в мс
animation.playbackRate = 2 // Скорость (2 = вдвое быстрее, -1 = назад)
// Состояние
console.log(animation.playState) // 'idle' | 'running' | 'paused' | 'finished'
console.log(animation.currentTime) // текущее время в мс
console.log(animation.effect.getTiming().duration) // длительностьanimation.addEventListener('finish', () => {
console.log('Анимация завершена')
})
animation.addEventListener('cancel', () => {
console.log('Анимация отменена')
})
// Promise-based API
animation.finished.then(() => {
console.log('Можно делать следующее действие')
})
animation.ready.then(() => {
console.log('Анимация готова к воспроизведению')
})// Создаём эффект отдельно от анимации
const effect = new KeyframeEffect(
element,
[
{ transform: 'scale(1)', offset: 0 }, // offset: 0–1 (как % в @keyframes)
{ transform: 'scale(1.2)', offset: 0.5 },
{ transform: 'scale(1)', offset: 1 },
],
{ duration: 600, easing: 'ease' }
)
const animation = new Animation(effect, document.timeline)
animation.play()Анимировать безопасно (через compositing, не вызывает layout):
transform (translate, scale, rotate)opacityfilterАнимировать дорого (вызывает layout/paint):
width, height, top, leftmargin, paddingfont-size// Правильно — анимируем transform
element.animate([
{ transform: 'translateY(0)' },
{ transform: 'translateY(-20px)' },
], { duration: 300, fill: 'forwards' })
// Дорого — анимируем height
element.animate([
{ height: '0px' },
{ height: '200px' },
], { duration: 300 }) // Вызывает layout на каждом кадре| CSS @keyframes | Web Animations API |
|-----------------------------|----------------------------------|
| Декларативно в CSS | Программно в JS |
| Нет pause/play из JS | Полное управление |
| Статические параметры | Динамические: duration, easing |
| animationend событие | Promise animation.finished |
| Нет доступа к прогрессу | currentTime / playbackRate |
Контроллер анимации через Web Animations API с play, pause, reverse и отображением прогресса
// Web Animations API — полный контроль над анимацией
const box = document.createElement('div')
box.style.cssText = `
width: 60px; height: 60px;
background: #7b2ff7;
border-radius: 8px;
margin: 20px;
`
document.body.appendChild(box)
// Создаём анимацию через element.animate()
const animation = box.animate(
[
{ transform: 'translateX(0px) rotate(0deg)', background: '#7b2ff7' },
{ transform: 'translateX(200px) rotate(180deg)', background: '#06b6d4' },
{ transform: 'translateX(0px) rotate(360deg)', background: '#7b2ff7' },
],
{
duration: 2000,
iterations: Infinity,
easing: 'ease-in-out',
}
)
// Панель управления
const controls = document.createElement('div')
controls.style.cssText = 'display: flex; gap: 8px; margin: 8px 20px; flex-wrap: wrap;'
document.body.appendChild(controls)
function makeBtn(label, onClick) {
const btn = document.createElement('button')
btn.textContent = label
btn.style.cssText = 'padding: 6px 12px; border-radius: 4px; border: 1px solid #7b2ff7; background: white; cursor: pointer; font-size: 13px;'
btn.onclick = onClick
controls.appendChild(btn)
return btn
}
makeBtn('Пауза', () => { animation.pause(); logState() })
makeBtn('Играть', () => { animation.play(); logState() })
makeBtn('Реверс', () => { animation.reverse(); logState() })
makeBtn('2x скорость', () => { animation.playbackRate = 2; logState() })
makeBtn('0.5x скорость', () => { animation.playbackRate = 0.5; logState() })
makeBtn('Перемотать на 50%', () => {
const timing = animation.effect.getTiming()
animation.currentTime = timing.duration * 0.5
logState()
})
const stateEl = document.createElement('pre')
stateEl.style.cssText = 'margin: 8px 20px; font-size: 12px; background: #1a202c; color: #a0aec0; padding: 8px; border-radius: 4px;'
document.body.appendChild(stateEl)
function logState() {
const timing = animation.effect.getTiming()
const progress = animation.currentTime !== null
? ((animation.currentTime % timing.duration) / timing.duration * 100).toFixed(1)
: 0
stateEl.textContent = [
`playState: ${animation.playState}`,
`currentTime: ${Math.round(animation.currentTime ?? 0)}ms`,
`progress: ${progress}%`,
`playbackRate: ${animation.playbackRate}x`,
`duration: ${timing.duration}ms`,
].join('\n')
}
// Обновляем состояние каждые 100ms
setInterval(logState, 100)
logState()
console.log('animation.playState:', animation.playState) // running
console.log('Используй кнопки для управления анимацией')Создай страницу с тремя анимированными карточками, которые появляются последовательно с задержкой (cascading). Используй `@keyframes fadeInUp` — карточки должны выезжать снизу вверх и проявляться. Каждая следующая карточка начинает анимацию на 150ms позже предыдущей через `animation-delay`.
В `fadeInUp`: `from` — `opacity: 0`, `translateY(20px)`; `to` — `opacity: 1`, `translateY(0)`. Длительность анимации `0.4s`. Задержки: первая — `0s`, вторая — `0.15s`, третья — `0.3s`. `animation-fill-mode: both` сохраняет конечное состояние.