Другое

CSS анимация: продолжить от текущего состояния для элементов

Узнайте, как заставить CSS‑анимацию продолжать от текущего состояния трансформации, а не сбрасывать её к началу. Решения для переходов между взаимодействиями.

Как заставить CSS‑анимацию продолжать с текущего состояния transform, а не сбрасываться к начальному значению?

У меня есть HTML‑элемент <div>, содержащий фотографию, который пользователь может перемещать щелчком и перетаскиванием. Когда элемент нажимается (:active), после небольшого задержки он слегка увеличивается. После отпускания кнопки он должен уменьшиться до 0. Задержка позволяет простому клику закрыть элемент без увеличения, тогда как удержание приводит к увеличению.

Проблема в том, что после нажатия, ожидания, удержания и отпускания элемент сжимается от 100 % вместо увеличенного состояния (1.2 в примере), что приводит к не плавному началу уменьшения.

Можно ли заставить переход к уменьшению начинаться с того уровня масштабирования, на котором находится элемент (будь то 1.0, 1.2 или любое промежуточное значение)?

Упрощённая версия имеет задержку в 1 секунду перед началом увеличения, занимает 2 с для увеличения и 2 с для уменьшения.

javascript
const thing = document.getElementById("thing");

thing.addEventListener("animationend", () => {
  thing.style.setProperty("animation-name", "none");
});
thing.addEventListener("click", () => {
  thing.style.setProperty("animation-name", "scale-down");
});
css
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); }
}
html
<!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 и динамически создать анимацию, начинающуюся с этого состояния.

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‑переходов вместо анимаций:

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);
}
javascript
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:

css
@keyframes scale-down {
  0% { transform: scale(1); }
  100% { transform: scale(0); }
}

div#thing {
  animation-fill-mode: forwards;
}

Однако, как отмечено в CSS-Tricks, это не решает проблему начальной точки, но помогает сохранить конечное состояние.


Практическая реализация

Ниже приведён полностью работающий пример, который захватывает текущее состояние transform:

html
<!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:

javascript
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:

javascript
thing.addEventListener("click", () => {
  const computedStyle = window.getComputedStyle(thing);
  const currentTransform = computedStyle.transform;
  
  // Сохраняем текущее transform как CSS‑переменную
  thing.style.setProperty('--current-transform', currentTransform);
});
css
@keyframes scale-down {
  0% { transform: var(--current-transform, scale(1)); }
  100% { transform: scale(0); }
}

Совместимость с браузерами

Большинство современных браузеров поддерживают вышеупомянутые техники:

  • getComputedStyle() и DOMMatrix широко поддерживаются
  • CSS‑переменные имеют отличную поддержку
  • Web Animations API поддерживается в большинстве современных браузеров, за исключением старых версий

Для максимальной совместимости рассмотрите использование библиотек, таких как GreenSock (GSAP), которые предоставляют кросс‑браузерные решения для анимаций и лучшее управление состоянием.


Лучшие практики

  1. Используйте переходы для простых изменений состояния: для базовых эффектов наведения и взаимодействия CSS‑переходы часто более подходящи, чем анимации.

  2. Очищайте динамически созданные таблицы стилей: всегда удаляйте динамически созданные стили, чтобы избежать утечек памяти.

  3. Учитывайте производительность: сложные transform‑анимации могут быть ресурсоёмкими. Используйте свойство will-change обдуманно:

    css
    div#thing {
      will-change: transform;
    }
    
  4. Тестируйте на реальных устройствах: взаимодействия с сенсорными экранами могут отличаться от мышиных.

  5. Предоставляйте резервные варианты: для критических анимаций обеспечьте простые резервные варианты для старых браузеров.

Как отмечено в SitePoint, «Свойство animation-fill-mode является ключевым для управления тем, как анимации ведут себя до и после их выполнения». Понимание этого свойства помогает создавать более предсказуемые анимационные поведения.


Источники

  1. Mozilla Developer Network – animation-fill-mode
  2. CSS-Tricks – CSS Animation Property
  3. SitePoint – Understanding CSS animation-fill-mode Property
  4. Stack Overflow – Maintaining final state at end of CSS animation
  5. Pragmatic Pineapple – Smoothly Reverting CSS Animations

Заключение

Чтобы CSS‑анимация продолжала работу с текущего состояния transform вместо сброса, необходимо:

  1. Захватить текущее состояние transform с помощью getComputedStyle().
  2. Создать динамические ключевые кадры, начинающиеся с текущего значения.
  3. Очищать динамически созданные стили, чтобы избежать утечек памяти.
  4. Рассмотреть использование CSS‑переходов для простых изменений состояния.
  5. Тщательно тестировать на разных типах взаимодействий (клик, удержание, сенсор).

Подход с JavaScript обеспечивает максимальный контроль и гибкость для сложных сценариев анимации, тогда как CSS‑переходы предлагают более простое решение для базовых взаимодействий.

Авторы
Проверено модерацией
Модерация