CSS анимация: продолжить от текущего состояния для элементов
Узнайте, как заставить CSS‑анимацию продолжать от текущего состояния трансформации, а не сбрасывать её к началу. Решения для переходов между взаимодействиями.
Как заставить CSS‑анимацию продолжать с текущего состояния transform, а не сбрасываться к начальному значению?
У меня есть HTML‑элемент <div>, содержащий фотографию, который пользователь может перемещать щелчком и перетаскиванием. Когда элемент нажимается (:active), после небольшого задержки он слегка увеличивается. После отпускания кнопки он должен уменьшиться до 0. Задержка позволяет простому клику закрыть элемент без увеличения, тогда как удержание приводит к увеличению.
Проблема в том, что после нажатия, ожидания, удержания и отпускания элемент сжимается от 100 % вместо увеличенного состояния (1.2 в примере), что приводит к не плавному началу уменьшения.
Можно ли заставить переход к уменьшению начинаться с того уровня масштабирования, на котором находится элемент (будь то 1.0, 1.2 или любое промежуточное значение)?
Упрощённая версия имеет задержку в 1 секунду перед началом увеличения, занимает 2 с для увеличения и 2 с для уменьшения.
const thing = document.getElementById("thing");
thing.addEventListener("animationend", () => {
thing.style.setProperty("animation-name", "none");
});
thing.addEventListener("click", () => {
thing.style.setProperty("animation-name", "scale-down");
});
div#thing {
position: absolute;
background-color: cyan;
outline: 1px solid black;
left: 25%;
top: 25%;
width: 50%;
height: 50%;
animation-name: none;
animation-duration: 2s;
}
div#thing:active {
transition: transform 2s;
transition-delay: 1s;
transform: scale(1.2);
}
@keyframes scale-down {
0% { transform: scale(1); }
100% { transform: scale(0); }
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Animation Test</title>
</head>
<body>
<h1>Animation Test</h1>
<div id="thing"></div>
</body>
</html>
Понимание проблемы
Основная проблема заключается в том, что анимации CSS всегда начинаются с заданных значений ключевых кадров (обычно 0%), независимо от текущего состояния transform элемента. Когда вы сочетаете псевдоклассы :active с анимациями, анимация сбрасывается к начальному состоянию вместо того, чтобы продолжить с того места, где пользователь остановил взаимодействие.
Согласно Mozilla Developer Network, «Свойство animation-fill-mode определяет, как CSS‑анимация должна применять стили к целевому элементу до и после её выполнения». Это свойство помогает контролировать поведение анимации, но не решает проблему начальной точки.
Ключевые решения
1. Использовать JavaScript для захвата текущего состояния transform
Самый надёжный подход — прочитать текущее значение transform с помощью JavaScript и динамически создать анимацию, начинающуюся с этого состояния.
const thing = document.getElementById("thing");
thing.addEventListener("animationend", () => {
thing.style.setProperty("animation-name", "none");
});
thing.addEventListener("click", () => {
// Получаем текущее значение transform
const computedStyle = window.getComputedStyle(thing);
const currentTransform = computedStyle.transform;
// Создаём динамический ключевой кадр, начинающийся с текущего состояния
const dynamicKeyframes = `
@keyframes scale-down-${Date.now()} {
0% { transform: ${currentTransform}; }
100% { transform: scale(0); }
}
`;
// Добавляем динамические ключевые кадры в документ
const styleSheet = document.createElement('style');
styleSheet.textContent = dynamicKeyframes;
document.head.appendChild(styleSheet);
// Применяем анимацию
thing.style.setProperty("animation-name", `scale-down-${Date.now()}`);
// Удаляем таблицу стилей после завершения анимации
thing.addEventListener("animationend", () => {
document.head.removeChild(styleSheet);
}, { once: true });
});
2. CSS‑переходы с правильным управлением состоянием
Для более плавных результатов рассмотрите использование CSS‑переходов вместо анимаций:
div#thing {
position: absolute;
background-color: cyan;
outline: 1px solid black;
left: 25%;
top: 25%;
width: 50%;
height: 50%;
transition: transform 2s ease-in-out;
}
div#thing:active {
transform: scale(1.2);
}
div#thing.scale-down {
transform: scale(0);
}
thing.addEventListener("click", (e) => {
if (e.type === "click") {
// Для простых кликов добавляем класс scale-down сразу
setTimeout(() => {
thing.classList.add("scale-down");
}, 100);
}
});
3. animation-fill-mode: forwards
Если нужно сохранить конечное состояние анимации, используйте animation-fill-mode: forwards:
@keyframes scale-down {
0% { transform: scale(1); }
100% { transform: scale(0); }
}
div#thing {
animation-fill-mode: forwards;
}
Однако, как отмечено в CSS-Tricks, это не решает проблему начальной точки, но помогает сохранить конечное состояние.
Практическая реализация
Ниже приведён полностью работающий пример, который захватывает текущее состояние transform:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Тест анимации</title>
<style>
div#thing {
position: absolute;
background-color: cyan;
outline: 1px solid black;
left: 25%;
top: 25%;
width: 50%;
height: 50%;
animation-name: none;
animation-duration: 2s;
transition: transform 2s ease-in-out;
}
div#thing:active {
transition-delay: 1s;
transform: scale(1.2);
}
</style>
</head>
<body>
<h1>Тест анимации</h1>
<div id="thing"></div>
<script>
const thing = document.getElementById("thing");
thing.addEventListener("animationend", () => {
thing.style.setProperty("animation-name", "none");
});
thing.addEventListener("click", (e) => {
// Получаем текущее значение transform
const computedStyle = window.getComputedStyle(thing);
const currentTransform = computedStyle.transform;
// Создаём динамические ключевые кадры
const uniqueId = Date.now();
const dynamicKeyframes = `
@keyframes scale-down-${uniqueId} {
0% { transform: ${currentTransform}; }
100% { transform: scale(0); }
}
`;
// Добавляем в документ
const styleSheet = document.createElement('style');
styleSheet.textContent = dynamicKeyframes;
document.head.appendChild(styleSheet);
// Применяем анимацию
thing.style.animationName = `scale-down-${uniqueId}`;
// Удаляем после завершения
thing.addEventListener("animationend", () => {
document.head.removeChild(styleSheet);
}, { once: true });
});
// Обработка простых кликов vs удержания
let clickTimer;
thing.addEventListener("mousedown", () => {
clickTimer = setTimeout(() => {
// Это удержание, анимация начнётся автоматически через :active
}, 1000); // порог 1 секунда
});
thing.addEventListener("mouseup", () => {
clearTimeout(clickTimer);
// Простой клик — сразу начинаем масштабирование вниз
thing.style.transform = "scale(0)";
});
thing.addEventListener("mouseleave", () => {
clearTimeout(clickTimer);
// Курсор покинул элемент — сразу начинаем масштабирование вниз
thing.style.transform = "scale(0)";
});
</script>
</body>
</html>
Продвинутые техники
1. Использование Web Animations API
Для большего контроля рассмотрите Web Animations API:
thing.addEventListener("click", () => {
const computedStyle = window.getComputedStyle(thing);
const currentTransform = computedStyle.transform;
// Разбираем матрицу transform
const matrix = new DOMMatrix(currentTransform);
const currentScale = matrix.m11; // Извлекаем масштаб из матрицы
thing.animate([
{ transform: `scale(${currentScale})` },
{ transform: 'scale(0)' }
], {
duration: 2000,
easing: 'ease-in-out'
});
});
2. CSS‑переменные для динамических значений
Используйте CSS‑переменные, чтобы передать значения из JavaScript в CSS:
thing.addEventListener("click", () => {
const computedStyle = window.getComputedStyle(thing);
const currentTransform = computedStyle.transform;
// Сохраняем текущее transform как CSS‑переменную
thing.style.setProperty('--current-transform', currentTransform);
});
@keyframes scale-down {
0% { transform: var(--current-transform, scale(1)); }
100% { transform: scale(0); }
}
Совместимость с браузерами
Большинство современных браузеров поддерживают вышеупомянутые техники:
getComputedStyle()иDOMMatrixшироко поддерживаются- CSS‑переменные имеют отличную поддержку
- Web Animations API поддерживается в большинстве современных браузеров, за исключением старых версий
Для максимальной совместимости рассмотрите использование библиотек, таких как GreenSock (GSAP), которые предоставляют кросс‑браузерные решения для анимаций и лучшее управление состоянием.
Лучшие практики
-
Используйте переходы для простых изменений состояния: для базовых эффектов наведения и взаимодействия CSS‑переходы часто более подходящи, чем анимации.
-
Очищайте динамически созданные таблицы стилей: всегда удаляйте динамически созданные стили, чтобы избежать утечек памяти.
-
Учитывайте производительность: сложные transform‑анимации могут быть ресурсоёмкими. Используйте свойство
will-changeобдуманно:cssdiv#thing { will-change: transform; } -
Тестируйте на реальных устройствах: взаимодействия с сенсорными экранами могут отличаться от мышиных.
-
Предоставляйте резервные варианты: для критических анимаций обеспечьте простые резервные варианты для старых браузеров.
Как отмечено в SitePoint, «Свойство animation-fill-mode является ключевым для управления тем, как анимации ведут себя до и после их выполнения». Понимание этого свойства помогает создавать более предсказуемые анимационные поведения.
Источники
- Mozilla Developer Network – animation-fill-mode
- CSS-Tricks – CSS Animation Property
- SitePoint – Understanding CSS animation-fill-mode Property
- Stack Overflow – Maintaining final state at end of CSS animation
- Pragmatic Pineapple – Smoothly Reverting CSS Animations
Заключение
Чтобы CSS‑анимация продолжала работу с текущего состояния transform вместо сброса, необходимо:
- Захватить текущее состояние
transformс помощьюgetComputedStyle(). - Создать динамические ключевые кадры, начинающиеся с текущего значения.
- Очищать динамически созданные стили, чтобы избежать утечек памяти.
- Рассмотреть использование CSS‑переходов для простых изменений состояния.
- Тщательно тестировать на разных типах взаимодействий (клик, удержание, сенсор).
Подход с JavaScript обеспечивает максимальный контроль и гибкость для сложных сценариев анимации, тогда как CSS‑переходы предлагают более простое решение для базовых взаимодействий.