Кнопка плавно меняет цвет при наведении. Карточка немного увеличивается при ховере. Спиннер крутится пока загружается данные. Уведомление выезжает снизу и плавно исчезает. Всё это — CSS-анимации и переходы. Без них интерфейс ощущается дёрганым и устаревшим.
transition делает изменение CSS-свойства плавным.
.btn {
background: #7b2ff7;
transition: background 0.2s ease;
}
.btn:hover {
background: #6b21d4;
}transition: свойство длительность timing-function задержка;
/* Примеры */
transition: background 0.3s ease;
transition: all 0.2s ease-in-out;
transition: background 0.2s, transform 0.1s; /* Несколько свойств */transition: ... ease; /* Медленно → быстро → медленно (по умолчанию) */
transition: ... ease-in; /* Медленно → быстро */
transition: ... ease-out; /* Быстро → медленно */
transition: ... ease-in-out; /* Медленно → быстро → медленно (симметрично) */
transition: ... linear; /* Равномерно */
transition: ... cubic-bezier(0.4, 0, 0.2, 1); /* Кастомная кривая (Material Design) */transform изменяет форму, положение и размер элемента не влияя на поток документа.
/* Сдвиг */
transform: translateX(20px);
transform: translateY(-10px);
transform: translate(20px, -10px); /* x и y */
transform: translateX(50%); /* 50% от ширины самого элемента */
/* Масштаб */
transform: scale(1.05); /* Увеличить на 5% */
transform: scale(0.95); /* Уменьшить на 5% */
transform: scaleX(1.2); /* Только по X */
/* Поворот */
transform: rotate(45deg); /* По часовой на 45° */
transform: rotate(-90deg); /* Против часовой на 90° */
/* Наклон */
transform: skewX(10deg);
/* Несколько трансформаций (порядок важен!) */
transform: translateX(-50%) rotate(45deg);.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Сдвинуть на -50% своей ширины и высоты */
}Для более сложных анимаций — с несколькими этапами — используй @keyframes.
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes shimmer {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
}animation: имя длительность timing-function задержка количество направление fill-mode;
.spinner {
animation: spin 1s linear infinite;
}
.card-appear {
animation: fadeIn 0.3s ease-out;
}
.loader {
animation: pulse 1.5s ease-in-out infinite;
}animation-name: fadeIn; /* Имя @keyframes */
animation-duration: 0.3s; /* Длительность */
animation-timing-function: ease; /* Кривая */
animation-delay: 0.1s; /* Задержка перед стартом */
animation-iteration-count: 1; /* Количество повторений (infinite — бесконечно) */
animation-direction: normal; /* normal | reverse | alternate | alternate-reverse */
animation-fill-mode: forwards; /* Что делать после окончания */
animation-play-state: running; /* running | paused */Браузер выполняет CSS быстрее, если анимируются только transform и opacity. Эти свойства обрабатываются на GPU.
/* Медленно — вызывает перерисовку (layout/paint) */
transition: width 0.3s;
transition: top 0.3s;
transition: margin 0.3s;
/* Быстро — только compositing (GPU) */
transition: transform 0.3s;
transition: opacity 0.3s;/* Подсказка браузеру заранее подготовить слой */
.animated-element {
will-change: transform, opacity;
}Не злоупотребляй will-change — каждый такой элемент создаёт отдельный GPU-слой, что увеличивает потребление памяти.
Ошибка 1: Анимировать width/height вместо transform
/* Плохо — перерисовывает layout */
.card:hover { width: 110%; }
/* Хорошо — только compositing */
.card:hover { transform: scale(1.05); }Ошибка 2: transition: all — плохая практика
transition: all 0.3s; /* Анимирует всё — включая display, что невозможно анимировать */
transition: background 0.3s, transform 0.2s; /* Явно указывай свойства */Ошибка 3: Слишком долгие анимации
transition: all 1s; /* 1 секунда — вечность для пользователя */
transition: all 0.2s; /* 200ms — воспринимается как мгновенное */Каждая кнопка на Wildberries, каждая карточка на Avito, каждое уведомление в Telegram Web — всё это CSS-анимации. Принцип: анимации должны ощущаться мгновенными (100-300ms) или намеренно медленными (скелетон-лоадер, 1-2s). Всё между — раздражает.
Framer Motion и GSAP используются для сложных React-анимаций, но под капотом те же CSS transform и opacity.
Переходы, трансформации и keyframe-анимации в действии
const style = document.createElement('style')
style.textContent = `
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; padding: 24px; background: #f7fafc; }
/* Кнопка с transition */
.btn {
background: #7b2ff7;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
margin: 8px;
}
.btn:hover {
background: #6b21d4;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(123,47,247,0.4);
}
.btn:active {
transform: translateY(0) scale(0.97);
box-shadow: none;
}
/* Спиннер через @keyframes */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e2e8f0;
border-top-color: #7b2ff7;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 16px;
}
/* Пульсирующий скелетон */
@keyframes shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
.skeleton {
height: 16px;
border-radius: 4px;
background: linear-gradient(90deg, #e2e8f0 25%, #f7fafc 50%, #e2e8f0 75%);
background-size: 800px 100%;
animation: shimmer 1.5s infinite;
margin: 8px 0;
}
/* Появление через fadeIn */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-card {
background: white;
border-radius: 12px;
padding: 16px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
margin: 8px 0;
animation: fadeInUp 0.4s ease-out forwards;
}
/* Карточка с hover transform */
.product-card {
display: inline-block;
background: white;
border-radius: 12px;
padding: 16px;
margin: 8px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.product-card:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
`
document.head.appendChild(style)
// Кнопки
const btn1 = document.createElement('button')
btn1.className = 'btn'
btn1.textContent = 'Наведи на меня'
document.body.appendChild(btn1)
// Спиннер загрузки
const spinnerLabel = document.createElement('p')
spinnerLabel.textContent = 'Спиннер загрузки:'
spinnerLabel.style.margin = '16px 0 0'
document.body.appendChild(spinnerLabel)
const spinner = document.createElement('div')
spinner.className = 'spinner'
document.body.appendChild(spinner)
// Скелетон
const skeletonLabel = document.createElement('p')
skeletonLabel.textContent = 'Скелетон-лоадер:'
document.body.appendChild(skeletonLabel)
;[200, 160, 120].forEach(width => {
const sk = document.createElement('div')
sk.className = 'skeleton'
sk.style.width = width + 'px'
document.body.appendChild(sk)
})
// Карточки с анимацией появления
const cardsLabel = document.createElement('p')
cardsLabel.textContent = 'Карточки с fadeInUp:'
document.body.appendChild(cardsLabel)
;['Урок 1: Введение', 'Урок 2: Переменные', 'Урок 3: Типы'].forEach((text, i) => {
const card = document.createElement('div')
card.className = 'fade-card'
card.style.animationDelay = (i * 0.1) + 's' // Каскадное появление
card.textContent = text
document.body.appendChild(card)
})
// Карточка товара
const productCard = document.createElement('div')
productCard.className = 'product-card'
productCard.innerHTML = '<strong>Nike Air</strong><br>4 990 ₽'
document.body.appendChild(productCard)
// Читаем стили анимации
const spinnerStyle = window.getComputedStyle(spinner)
console.log('Spinner animation-name:', spinnerStyle.animationName) // spin
console.log('Spinner animation-duration:', spinnerStyle.animationDuration) // 0.8s
console.log('Spinner animation-iteration:', spinnerStyle.animationIterationCount) // infinite
const btnStyle = window.getComputedStyle(btn1)
console.log('Btn transition:', btnStyle.transition)Кнопка плавно меняет цвет при наведении. Карточка немного увеличивается при ховере. Спиннер крутится пока загружается данные. Уведомление выезжает снизу и плавно исчезает. Всё это — CSS-анимации и переходы. Без них интерфейс ощущается дёрганым и устаревшим.
transition делает изменение CSS-свойства плавным.
.btn {
background: #7b2ff7;
transition: background 0.2s ease;
}
.btn:hover {
background: #6b21d4;
}transition: свойство длительность timing-function задержка;
/* Примеры */
transition: background 0.3s ease;
transition: all 0.2s ease-in-out;
transition: background 0.2s, transform 0.1s; /* Несколько свойств */transition: ... ease; /* Медленно → быстро → медленно (по умолчанию) */
transition: ... ease-in; /* Медленно → быстро */
transition: ... ease-out; /* Быстро → медленно */
transition: ... ease-in-out; /* Медленно → быстро → медленно (симметрично) */
transition: ... linear; /* Равномерно */
transition: ... cubic-bezier(0.4, 0, 0.2, 1); /* Кастомная кривая (Material Design) */transform изменяет форму, положение и размер элемента не влияя на поток документа.
/* Сдвиг */
transform: translateX(20px);
transform: translateY(-10px);
transform: translate(20px, -10px); /* x и y */
transform: translateX(50%); /* 50% от ширины самого элемента */
/* Масштаб */
transform: scale(1.05); /* Увеличить на 5% */
transform: scale(0.95); /* Уменьшить на 5% */
transform: scaleX(1.2); /* Только по X */
/* Поворот */
transform: rotate(45deg); /* По часовой на 45° */
transform: rotate(-90deg); /* Против часовой на 90° */
/* Наклон */
transform: skewX(10deg);
/* Несколько трансформаций (порядок важен!) */
transform: translateX(-50%) rotate(45deg);.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Сдвинуть на -50% своей ширины и высоты */
}Для более сложных анимаций — с несколькими этапами — используй @keyframes.
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes shimmer {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
}animation: имя длительность timing-function задержка количество направление fill-mode;
.spinner {
animation: spin 1s linear infinite;
}
.card-appear {
animation: fadeIn 0.3s ease-out;
}
.loader {
animation: pulse 1.5s ease-in-out infinite;
}animation-name: fadeIn; /* Имя @keyframes */
animation-duration: 0.3s; /* Длительность */
animation-timing-function: ease; /* Кривая */
animation-delay: 0.1s; /* Задержка перед стартом */
animation-iteration-count: 1; /* Количество повторений (infinite — бесконечно) */
animation-direction: normal; /* normal | reverse | alternate | alternate-reverse */
animation-fill-mode: forwards; /* Что делать после окончания */
animation-play-state: running; /* running | paused */Браузер выполняет CSS быстрее, если анимируются только transform и opacity. Эти свойства обрабатываются на GPU.
/* Медленно — вызывает перерисовку (layout/paint) */
transition: width 0.3s;
transition: top 0.3s;
transition: margin 0.3s;
/* Быстро — только compositing (GPU) */
transition: transform 0.3s;
transition: opacity 0.3s;/* Подсказка браузеру заранее подготовить слой */
.animated-element {
will-change: transform, opacity;
}Не злоупотребляй will-change — каждый такой элемент создаёт отдельный GPU-слой, что увеличивает потребление памяти.
Ошибка 1: Анимировать width/height вместо transform
/* Плохо — перерисовывает layout */
.card:hover { width: 110%; }
/* Хорошо — только compositing */
.card:hover { transform: scale(1.05); }Ошибка 2: transition: all — плохая практика
transition: all 0.3s; /* Анимирует всё — включая display, что невозможно анимировать */
transition: background 0.3s, transform 0.2s; /* Явно указывай свойства */Ошибка 3: Слишком долгие анимации
transition: all 1s; /* 1 секунда — вечность для пользователя */
transition: all 0.2s; /* 200ms — воспринимается как мгновенное */Каждая кнопка на Wildberries, каждая карточка на Avito, каждое уведомление в Telegram Web — всё это CSS-анимации. Принцип: анимации должны ощущаться мгновенными (100-300ms) или намеренно медленными (скелетон-лоадер, 1-2s). Всё между — раздражает.
Framer Motion и GSAP используются для сложных React-анимаций, но под капотом те же CSS transform и opacity.
Переходы, трансформации и keyframe-анимации в действии
const style = document.createElement('style')
style.textContent = `
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; padding: 24px; background: #f7fafc; }
/* Кнопка с transition */
.btn {
background: #7b2ff7;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
margin: 8px;
}
.btn:hover {
background: #6b21d4;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(123,47,247,0.4);
}
.btn:active {
transform: translateY(0) scale(0.97);
box-shadow: none;
}
/* Спиннер через @keyframes */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid #e2e8f0;
border-top-color: #7b2ff7;
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 16px;
}
/* Пульсирующий скелетон */
@keyframes shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
.skeleton {
height: 16px;
border-radius: 4px;
background: linear-gradient(90deg, #e2e8f0 25%, #f7fafc 50%, #e2e8f0 75%);
background-size: 800px 100%;
animation: shimmer 1.5s infinite;
margin: 8px 0;
}
/* Появление через fadeIn */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-card {
background: white;
border-radius: 12px;
padding: 16px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
margin: 8px 0;
animation: fadeInUp 0.4s ease-out forwards;
}
/* Карточка с hover transform */
.product-card {
display: inline-block;
background: white;
border-radius: 12px;
padding: 16px;
margin: 8px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.product-card:hover {
transform: translateY(-4px) scale(1.02);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
`
document.head.appendChild(style)
// Кнопки
const btn1 = document.createElement('button')
btn1.className = 'btn'
btn1.textContent = 'Наведи на меня'
document.body.appendChild(btn1)
// Спиннер загрузки
const spinnerLabel = document.createElement('p')
spinnerLabel.textContent = 'Спиннер загрузки:'
spinnerLabel.style.margin = '16px 0 0'
document.body.appendChild(spinnerLabel)
const spinner = document.createElement('div')
spinner.className = 'spinner'
document.body.appendChild(spinner)
// Скелетон
const skeletonLabel = document.createElement('p')
skeletonLabel.textContent = 'Скелетон-лоадер:'
document.body.appendChild(skeletonLabel)
;[200, 160, 120].forEach(width => {
const sk = document.createElement('div')
sk.className = 'skeleton'
sk.style.width = width + 'px'
document.body.appendChild(sk)
})
// Карточки с анимацией появления
const cardsLabel = document.createElement('p')
cardsLabel.textContent = 'Карточки с fadeInUp:'
document.body.appendChild(cardsLabel)
;['Урок 1: Введение', 'Урок 2: Переменные', 'Урок 3: Типы'].forEach((text, i) => {
const card = document.createElement('div')
card.className = 'fade-card'
card.style.animationDelay = (i * 0.1) + 's' // Каскадное появление
card.textContent = text
document.body.appendChild(card)
})
// Карточка товара
const productCard = document.createElement('div')
productCard.className = 'product-card'
productCard.innerHTML = '<strong>Nike Air</strong><br>4 990 ₽'
document.body.appendChild(productCard)
// Читаем стили анимации
const spinnerStyle = window.getComputedStyle(spinner)
console.log('Spinner animation-name:', spinnerStyle.animationName) // spin
console.log('Spinner animation-duration:', spinnerStyle.animationDuration) // 0.8s
console.log('Spinner animation-iteration:', spinnerStyle.animationIterationCount) // infinite
const btnStyle = window.getComputedStyle(btn1)
console.log('Btn transition:', btnStyle.transition)Создай анимацию прыгающего мяча с помощью `@keyframes bounce`. Мяч должен подниматься на 20px вверх (translateY(-20px)) в середине анимации и возвращаться обратно. Добавь спиннер загрузки, который крутится бесконечно через `@keyframes spin`.
Для `bounce`: 0% и 100% — `translateY(0)`, 50% — `translateY(-20px)`. Длительность анимации `0.6s`. Для `spin`: `from` — `rotate(0deg)`, `to` — `rotate(360deg)`, длительность `1s`.