Transitions и animations

Библиотека Shopify Draggable предоставляет мощный инструментарий для работы с перетаскиваемыми элементами в веб-интерфейсах. Одним из ключевых аспектов пользовательского опыта является плавность перемещения элементов и визуальная обратная связь, которую обеспечивают transitions и animations. Эти механизмы позволяют создать ощущение естественности и интерактивности при взаимодействии с элементами списка, сетки или канбан-доски.


Использование CSS transitions

Shopify Draggable не накладывает ограничения на использование CSS-переходов. Наиболее часто применяются свойства transform, opacity, top, left и height/width, так как они наиболее оптимизированы для производительности в современных браузерах.

Пример добавления плавного смещения:

.draggable {
  transition: transform 0.3s ease, opacity 0.2s ease;
}

Здесь:

  • transform 0.3s ease обеспечивает плавное перемещение элемента при изменении его координат через Draggable.
  • opacity 0.2s ease позволяет сделать появление/исчезновение более естественным.

Важный момент: при использовании CSS transitions с Draggable необходимо учитывать, что библиотека сама управляет координатами элементов через inline styles с помощью transform: translate3d(x, y, 0). Поэтому лучше избегать одновременной анимации top/left, чтобы не создавать конфликты рендеринга.


Анимация перестановки элементов (Sortables)

Для sortable контейнеров Draggable предоставляет встроенные хуки, позволяющие реализовать анимацию перестановки элементов. Основные события:

  • sortable:sorted — вызывается после завершения сортировки.
  • sortable:sort — во время движения элемента.
  • sortable:start и sortable:stop — начало и завершение перетаскивания.

Пример плавной анимации перестановки:

const container = document.querySelector('.sortable-container');

const sortable = new Draggable.Sortable(container, {
  draggable: '.draggable',
  mirror: {
    constrainDimensions: true,
  },
});

sortable.on('sortable:sort', (event) => {
  const {oldIndex, newIndex, source} = event;

  // Добавление CSS-класса для плавного перемещения
  source.classList.add('animating');

  requestAnimationFrame(() => {
    source.classList.remove('animating');
  });
});

И соответствующий CSS:

.draggable.animating {
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

Использование requestAnimationFrame гарантирует корректное применение анимации без мерцаний и заиканий при изменении DOM.


Mirror-элементы и их анимация

Draggable создает отдельный элемент mirror для перетаскивания (mirror: true по умолчанию). Этот элемент копирует внешний вид оригинала и движется поверх интерфейса, что позволяет визуально отделить процесс перемещения от реального DOM.

  • Анимация mirror осуществляется через CSS transform.
  • Для плавного эффекта можно использовать transition на свойство transform самого mirror:
.draggable-mirror {
  transition: transform 0.2s ease;
}
  • Mirror поддерживает динамическое масштабирование, opacity и другие визуальные эффекты. Например, уменьшение прозрачности при старте перетаскивания:
sortable.on('drag:start', (event) => {
  event.data.mirror.style.opacity = '0.8';
});

sortable.on('drag:stop', (event) => {
  event.data.mirror.style.opacity = '';
});

Анимации на базе FLIP

Метод FLIP (First, Last, Invert, Play) позволяет создавать плавные перестановки элементов без резких скачков. Draggable совместим с этим подходом через вычисление начальной и конечной позиции элементов.

Пример:

sortable.on('sortable:sorted', ({oldIndex, newIndex, source, containers}) => {
  const firstRect = source.getBoundingClientRect();

  // DOM уже обновлен
  const lastRect = source.getBoundingClientRect();

  const invertX = firstRect.left - lastRect.left;
  const invertY = firstRect.top - lastRect.top;

  source.style.transform = `translate(${invertX}px, ${invertY}px)`;

  requestAnimationFrame(() => {
    source.style.transition = 'transform 300ms ease';
    source.style.transform = '';
  });
});

Этот метод позволяет добиться эффекта «элемент плавно перемещается на новое место», сохраняя высокую производительность.


Пользовательские easing-функции и скорость анимации

Для достижения более динамичных и естественных переходов можно использовать cubic-bezier кривые. Например:

.draggable {
  transition: transform 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
}
  • ease-in-out – замедление в начале и в конце.
  • cubic-bezier(0.25, 1, 0.5, 1) – создаёт эффект «отскока».
  • Длительность анимации зависит от контекста интерфейса: для крупных сеток лучше увеличивать время до 400–500 мс, чтобы пользователь мог визуально отслеживать движение.

Сложные комбинации анимаций

Можно комбинировать mirror-анимации, FLIP, opacity transitions и scale эффекты. Пример:

sortable.on('drag:start', ({data}) => {
  data.mirror.style.transition = 'transform 0.2s ease, opacity 0.2s ease';
  data.mirror.style.opacity = '0.7';
  data.mirror.style.transform += ' scale(1.05)';
});

sortable.on('drag:stop', ({data}) => {
  data.mirror.style.transition = 'transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s';
  data.mirror.style.opacity = '1';
  data.mirror.style.transform = data.mirror.style.transform.replace(' scale(1.05)', '');
});

Такой подход создаёт ощущение «приподнятого» элемента, который плавно возвращается в исходное состояние после завершения перетаскивания.


Итоговые рекомендации по производительности

  • Анимировать transform и opacity вместо top/left для GPU-ускорения.
  • Использовать requestAnimationFrame при сложных последовательных изменениях DOM.
  • Минимизировать количество одновременно анимируемых элементов, особенно в крупных списках.
  • Применять mirror для визуальной обратной связи и отделения перетаскиваемого элемента от DOM.

Понимание и правильное сочетание transitions, mirror-анимаций и FLIP-подхода позволяет создавать интерфейсы, где перемещение элементов ощущается естественным, отзывчивым и визуально привлекательным.