Перейти к содержимому
Поиск
Разработка

Motion: анимации интерфейсов на JavaScript — 9 живых примеров

Движение для понятных интерфейсов: девять рабочих сценариев с настоящей Motion. Настраивайте переходы и скачивайте автономный HTML или весь проект.

Александр Некрасов
Motion — анимация интерфейсов: карточки, переходы, прогресс и пошаговая форма

Нажали кнопку, отфильтровали карточки, передвинули задачу выше — интерфейс уже изменился. Но заметил ли человек результат? Понял ли, куда исчез элемент и какой раздел теперь открыт? Небольшое движение помогает связать действие с новым состоянием, если оно не задерживает работу и не подменяет настоящий результат красивым эффектом.

Motion позволяет управлять такими переходами на обычном JavaScript. Ни React, ни большой фреймворк для этого не обязательны. Ниже — девять полноценных интерфейсных примеров: их можно менять прямо на странице, изучать исходники и скачивать автономным HTML с выбранными настройками. В конце разберём запуск под Windows, Linux и размещение статической мастерской на VPS.

Что такое Motion и какую задачу она решает

Motion — библиотека анимации для веба. В этой статье используется JavaScript API пакета motion: функция animate меняет свойства элементов, а вспомогательные инструменты помогают организовать несколько переходов. Можно управлять прозрачностью, перемещением, размером, пружинным откликом и другими визуальными параметрами. Наши примеры работают с настоящей библиотекой, собранной в локальный JavaScript.

Полезно сразу разделить два слоя. Код приложения выбирает активную вкладку, проверяет поле, переставляет записи и рассчитывает сумму. Motion показывает переход между состояниями. Если убрать движение, результат должен остаться правильным: выбранная вкладка всё ещё выбрана, закрытый раздел не получает фокус, отменённый расчёт не становится готовым отчётом.

Такой подход подходит кабинетам клиентов, административным панелям, каталогам услуг и инструментам команды. Начните с места, где человек теряет контекст: перестановка приоритетов, смена фильтра, открытие деталей. Анимация ради самого движения обычно добавляет работу браузеру и внимание пользователя, не объясняя ничего нового.

Официальная документация: установка и первый переход.

Когда достаточно CSS, а когда полезна библиотека

Для простого изменения цвета кнопки при hover часто достаточно CSS transition. Его легко поддерживать, он не требует отдельной зависимости и хорошо подходит постоянным состояниям интерфейса. Если задача состоит только в готовом декоративном эффекте, на сайте есть мастерская Animista с CSS-анимациями.

Motion становится удобной, когда значения известны только во время работы: новая позиция карточки, измеренная высота ответа, ширина выбранной вкладки, результат предыдущего действия. В JavaScript проще связать окончание движения с очисткой, отменить устаревший переход и применить одинаковые настройки к нескольким компонентам.

При этом библиотека не заменяет HTML-семантику, клавиатуру и управление состоянием. Например, сделать панель прозрачной недостаточно, чтобы закрыть диалог. Элемент может продолжать перехватывать нажатия и фокус. В мастерской завершение визуального перехода согласовано с hidden, inert, состоянием native dialog и порядком элементов DOM.

Версия, бесплатные возможности и лицензия

В проекте закреплена Motion 14.10.1, Bootstrap 5.3.8 и сборщик esbuild 0.28.2. Актуальность пакета сверена при подготовке; package-lock.json фиксирует дерево зависимостей. В готовом архиве уже лежит собранный public/app.js. При открытии мастерской браузер не загружает библиотеку с внешнего CDN.

Мы используем бесплатный пакет motion с лицензией MIT. Платные Motion+ компоненты и отдельные коммерческие наборы не нужны. Официальный сайт содержит разные продукты и примеры, поэтому перед переносом понравившегося решения смотрите, к какому пакету и условиям оно относится. Наличие похожего названия не делает любую дополнительную библиотеку частью нашего архива.

Собственный код и учебные данные также доступны под MIT. THIRD-PARTY-LICENSES.txt содержит уведомления Motion и её зависимостей вместе с Bootstrap. Сохраняйте лицензии при переносе. Скачиваемый автономный HTML тоже содержит эти тексты, поскольку он включает саму библиотеку, а не только ссылку на неё.

npm install --save-exact motion@14.10.1

Официальная документация: лицензия Motion.

Мини-мастерская: девять интерфейсных сценариев

Выберите сценарий в списке, прочитайте его назначение и выполните предложенное действие. Длительность и кривая позволяют сравнить быстрый переход с более заметным. Переключатель уменьшения движения сохраняет всю функциональность. Если такая настройка включена в операционной системе, мастерская уважает её независимо от положения переключателя.

«Завершить движение» доводит текущие переходы до конечного состояния. Данные при этом сохраняются. «Сбросить пример» создаёт исходный вариант заново: удаляет сообщения, возвращает порядок задач и отменяет незавершённые локальные ожидания. Для расчёта отчёта есть отдельная кнопка отмены — завершение анимации полосы не должно отменять бизнес-операцию.

В каждом сценарии есть место применения, короткое действие для первого опыта и исходный JS-модуль. Автономный HTML содержит выбранный сценарий, настройки, стили и настоящую Motion. Он открывается без сборки и без сети. Текущие данные внутри примера, например написанное название проекта или переставленный список, в этот HTML не переносятся: он начинает с учебного исходного состояния.

Данные учебные. Внутри iframe есть собственная прокрутка; на телефоне можно открыть мастерскую отдельно. Все анимации выполняет настоящая локальная Motion.

Первый animate: элемент, свойства и секунды

Начать можно с обычного элемента и двух свойств: прозрачности и вертикального смещения. Вариант с массивом задаёт начальное и конечное значение. Время указывается в секундах: 0.3 означает триста миллисекунд. В мастерской ползунок подписан в миллисекундах, поэтому общий модуль переводит значение перед вызовом библиотеки.

Передавайте конкретный элемент, когда компонент живёт внутри большого интерфейса. Глобальный селектор вроде button способен затронуть чужие кнопки, особенно если на странице несколько экземпляров виджета. Наши сценарии получают свой root и ищут элементы только внутри него. Это упрощает встраивание в статью, вкладку кабинета или модальное окно.

Импорт из npm сам по себе не выполняется обычной HTML-страницей. В проекте esbuild собирает модули и библиотеку в браузерный app.js. При добавлении в существующий PHP-сайт вы можете использовать эту готовую сборку или подключить исходники к своему сборщику. PHP не должен рассчитывать кадры анимации: они выполняются на устройстве посетителя.

import { animate } from 'motion';
const panel = document.querySelector('#panel');
const controls = animate(panel, {
  opacity: [0, 1],
  y: [12, 0]
}, { duration: 0.3, ease: 'easeOut' });
await controls;
console.log('Переход завершён');

Официальная документация: animate, параметры и управление.

1. Отклик кнопки: действие раньше украшения

Первый пример добавляет учебную услугу в локальный список. Счётчик меняется сразу, а кнопка слегка сжимается и возвращается к нормальному размеру. Это подтверждение уже выполненного действия. Оно не изображает сохранение на сервере, которого не было, и не заставляет ждать окончание пружины ради нового нажатия.

Сравните обычную кривую и упругий отклик. У пружины заданы type: spring, длительность и умеренный bounce. Общий easing на форму этой пружины не влияет; это прямо указано рядом с настройками. Большой отскок на каждом нажатии быстро утомляет, особенно когда действие повторяется десятки раз подряд.

Такой приём подходит избранному, количеству товара и переключению локального режима. При настоящем запросе различайте отправку, ожидание, подтверждённый успех и ошибку. Анимация зелёной галочки до ответа API может создать ложное ощущение, что данные сохранены. При повторном нажатии старое движение завершается, поэтому кнопка не накапливает независимые конкурирующие переходы.

count += 1;
counter.textContent = `${count} услуг`;
animate(button, { scale: [0.94, 1] }, {
  type: 'spring', duration: 0.42, bounce: 0.25
});

2. Уведомления: появление, закрытие и фокус

Сообщение о готовом экспорте должно быть заметным, но не обязано блокировать всю страницу. Во втором сценарии уведомление появляется с небольшим смещением. Можно добавить до трёх сообщений, после чего кнопка временно отключается. Закрытие освобождает место и возвращает возможность добавить следующее сообщение.

Важная информация не исчезает по таймеру. Это помогает человеку спокойно прочитать текст и пользоваться клавиатурой. У кнопки закрытия есть доступное название с номером уведомления. После удаления фокус возвращается на доступную кнопку добавления; он не остаётся на удалённом элементе. Само движение не является единственным сигналом: общий статус сообщает о появлении и закрытии.

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

await animate(message, { opacity: 0, x: 24 }, { duration: 0.25 });
message.remove();
addButton.disabled = false;
addButton.focus();

3. Accordion: высота и доступность содержимого

Раскрывающийся раздел удобен для вопросов, деталей тарифа и дополнительных настроек. В нашем примере каждый ответ независим: можно открыть несколько. Кнопка сообщает aria-expanded и ссылается на содержимое через aria-controls. Закрытый ответ скрыт, а во время закрытия исключается из взаимодействия с помощью inert.

Высоту нельзя всегда заменить условными пятьюстами пикселями. Текст меняется, перевод становится длиннее, на телефоне строк больше. Поэтому код измеряет scrollHeight и анимирует фактическую высоту. После открытия ставится height:auto, чтобы последующее изменение содержимого не обрезалось. При закрытии конечная высота нулевая, затем применяется hidden.

Попробуйте быстро нажать заголовок несколько раз. Последний выбор должен победить. В сценарии есть номер поколения перехода: запоздалое завершение предыдущего открытия не отменяет новое закрытие. Это маленький пример общей проблемы асинхронных интерфейсов. Красивый переход без контроля актуальности иногда делает компонент менее надёжным, чем мгновенное переключение.

answer.hidden = false;
answer.inert = false;
await animate(answer, { height: [0, answer.scrollHeight] }, {
  duration: 0.3
});
answer.style.height = 'auto';
// Полный сценарий также проверяет актуальность перехода.

4. Вкладки: движущийся индикатор и клавиатура

Полоса под выбранной вкладкой показывает смену контекста. Её новая позиция и ширина измеряются по кнопке, поэтому название не обязано иметь фиксированную длину. Одновременно меняется видимая панель. Движение помогает проследить выбор, а содержимое и ARIA-состояния обновляются по логике приложения.

Вкладки поддерживают стрелки влево и вправо, Home и End. Выбранная кнопка участвует в обычном Tab-порядке, остальные получают tabindex:-1. После автоматического переключения клавиатурой фокус остаётся на выбранной вкладке, а следующая клавиша Tab позволяет перейти в содержимое. Нажатие мышью выполняет тот же выбор.

ResizeObserver отслеживает ширину группы. Это необходимо не только при повороте телефона: рабочая область меняется после открытия бокового меню или изменения размера встроенного блока. Если наблюдатель не отключить при удалении компонента, старый экземпляр продолжит выполнять ненужную работу. В общем cleanup мастерской такие наблюдатели освобождаются вместе со сценарием.

animate(indicator, {
  x: selectedTab.offsetLeft,
  width: selectedTab.offsetWidth
}, { duration: 0.25, ease: 'easeOut' });
selectedTab.setAttribute('aria-selected', 'true');

5. Фильтрация карточек и техника FLIP

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

Для этого используется небольшой явный FLIP-приём: запомнить прямоугольники до изменения DOM, применить фильтр, измерить новые положения и анимировать разницу к нулю. Реальное размещение уже выполнено браузером. Transform временно создаёт впечатление движения из старой позиции. Это наш код поверх бесплатного animate, а не скрытое подключение платной layout-функции.

На маленьких наборах подход понятен и удобен. Для тысяч строк измерять каждый элемент при каждом вводе дорого. Там понадобятся ограничение частоты, виртуализация или другой дизайн. Также не задерживайте фактический фильтр ради завершения длинного вылета: пользователь уже ввёл новый запрос и ожидает актуальные результаты, а не очередь старых анимаций.

const before = card.getBoundingClientRect();
changeLayout();
const after = card.getBoundingClientRect();
animate(card, {
  x: [before.left - after.left, 0],
  y: [before.top - after.top, 0]
}, { duration: 0.3 });

6. Перестановка списка без обязательного drag

Перетаскивание выглядит естественно, но не всегда удобно клавиатурой или на узком экране. В списке приоритетов используются явные кнопки вверх и вниз. Они меняют массив и порядок DOM, а FLIP показывает перемещение. Первая задача не может уйти выше начала, последняя — ниже конца. Ограничения видны по отключённым кнопкам.

После перестановки фокус остаётся рядом с выбранной задачей. Если прежняя кнопка стала недоступной на границе, выбирается другая доступная кнопка этого элемента. Общий статус сообщает название и новую позицию. Важно, что порядок чтения соответствует показанному порядку: одного CSS transform без перестановки DOM для этого недостаточно.

Кнопка скачивания сохраняет текущий массив в JSON. Откройте файл и сравните его с экраном. В реальном сервисе понадобится серверное сохранение с правами доступа и контролем конфликтов, если список одновременно редактируют несколько людей. Успешное визуальное перемещение само по себе не доказывает, что новый порядок записан в базе.

const next = [...items];
[next[index], next[index + 1]] = [next[index + 1], next[index]];
// Затем переставить соответствующие DOM-узлы и выполнить FLIP.
items = next;

7. Прогресс: настоящие данные вместо выдуманного процента

В этом опыте браузер действительно складывает суммы 240 синтетических строк. Работа разбита на пакеты по двадцать четыре записи. Между пакетами есть короткая пауза, чтобы посетитель успел увидеть изменение и попробовать отмену. Это учебная организация расчёта, а не обещание высокой производительности или имитация ответа сервера.

После каждого пакета известно точное число обработанных строк. Оно обновляет aria-valuenow, текст и ширину полосы. Финальная сумма равна 681360 рублей; JSON содержит количество строк, общий итог и разбиение по группам. Суммы групп сходятся с общей. Можно проверить результат независимо от плавности полосы.

Отмена запрещает выдавать частичный результат как готовый. Повторный запуск получает новое поколение операции, поэтому старый ожидающий callback не завершает новый расчёт. Смена сценария тоже отменяет локальное ожидание. Для настоящей загрузки с сервера используйте фактический прогресс или неопределённый индикатор: плавно довести произвольный таймер до девяноста девяти процентов — ещё не измерить работу.

processed.push(...rows.slice(offset, offset + 24));
progress.setAttribute('aria-valuenow', String(processed.length));
animate(fill, { width: `${processed.length / rows.length * 100}%` }, {
  duration: 0.1
});

8. Боковая панель: Motion внутри native dialog

Боковая панель позволяет открыть небольшую заявку, не уходя из списка. Внутри находится учебный приоритет. После применения основная карточка показывает новое значение. Это локальное состояние примера: база и внешний API не вызываются. Так видно, какую часть интерфейса обновляет действие пользователя.

Для модальности используется HTML dialog, открытый через showModal. Браузер обеспечивает отдельный модальный слой и управление взаимодействием с остальной страницей. Motion анимирует внутреннее содержимое панели. Кнопка закрытия и Escape запускают один и тот же путь, после чего вызывается close и фокус возвращается на кнопку открытия.

Не собирайте модальность только из полупрозрачного прямоугольника. Без ограничения фокуса клавиша Tab может уйти за панель, а скринридер продолжит читать фон как обычную страницу. Даже с native dialog полезна проверка в целевых браузерах. Длинная панель должна прокручиваться сама, а кнопка закрытия оставаться доступной на маленьком экране.

dialog.showModal();
animate(drawerBody, { x: [40, 0], opacity: [0, 1] }, {
  duration: 0.3
});
dialog.addEventListener('cancel', event => {
  event.preventDefault();
  closeWithAnimation();
});

9. Пошаговая форма: переход не заменяет проверку

Последний сценарий собирает название и формат учебного проекта. На первом шаге проверяется длина названия после удаления пробелов по краям. Неверное значение не пропускает пользователя дальше: рядом появляется пояснение, поле получает aria-invalid и фокус. Движение следующего шага запускается только после успешной проверки.

Выбранные значения сохраняются при возврате. На третьем шаге человек видит итог перед скачиванием JSON. Никакой серверный проект при этом не создаётся; кнопка названа по настоящему результату. Для реального приложения это место подтверждения, отправки запроса, ожидания и обработки возможной ошибки, а не просто финальная красивая карточка.

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

const name = titleInput.value.trim();
if (name.length < 2 || name.length > 40) {
  titleInput.setAttribute('aria-invalid', 'true');
  titleInput.focus();
  return;
}
// Только теперь показать следующий шаг и его переход.

Длительность, easing и пружина: выбираем по смыслу

Длинный переход заметнее, но не обязательно понятнее. Небольшой отклик кнопки обычно требует меньше времени, чем появление крупной панели. Ползунок мастерской специально позволяет сделать движение слишком медленным: сравните, когда оно начинает мешать. Значение выбирают по характеру действия, устройству и частоте использования, а не по единому красивому числу для всего сайта.

Линейная кривая сохраняет темп, easeOut плавно останавливается, easeInOut разгоняется и тормозит. Пружина описывает другой характер движения. В duration-based варианте используются duration и bounce; физическая пружина может задаваться stiffness, damping и mass. Не смешивайте параметры без понимания приоритета: настройка физики способна изменить ожидания от длительности.

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

animate(element, { x: 0 }, { duration: 0.4, ease: 'easeInOut' });
animate(element, { scale: 1 }, { type: 'spring', duration: 0.4, bounce: 0.2 });

Stagger: последовательное появление без длинной очереди

Когда несколько карточек появляются с небольшой разницей во времени, взгляд легче считывает группу. В каталоге применяется stagger: библиотека вычисляет задержку каждого элемента. Это реальный вызов Motion, а не заранее записанный ролик. Элементы уже существуют и доступны по смыслу; переход лишь делает появление более последовательным.

Задержки складываются. При сотне карточек даже небольшой интервал превращает последний элемент в долгожданный результат. Ограничивайте размер группы и общую длительность. В рабочем интерфейсе удобно анимировать первые несколько видимых элементов, а остальное показывать сразу. Фильтрация также не должна ждать, пока закончит предыдущая очередь появления.

В режиме уменьшенного движения задержка становится нулевой. Если карточка содержит важную кнопку, не оставляйте её невидимой, но уже доступной фокусу, на несколько секунд. Подобные эффекты требуют согласования визуального и интерактивного состояния, а не только красивой настройки easing.

import { animate, stagger } from 'motion';
animate(cards, { opacity: [0, 1], y: [12, 0] }, {
  duration: 0.3, delay: stagger(0.045)
});

Официальная документация: последовательная задержка элементов.

Stop, complete и cleanup: что делать при смене страницы

У управления анимацией разные значения. Complete доводит её до конечного состояния. Stop прекращает движение на текущих значениях; остановленный экземпляр не предназначен для повторного запуска. Cancel возвращает начальное состояние. Нельзя подставлять эти методы друг вместо друга, не решив, какой интерфейс должен увидеть человек после остановки.

Мастерская использует complete для кнопки завершения движения. При удалении сценария общий scope останавливает анимации, отменяет собственные таймеры, отключает слушатели и ResizeObserver. Ожидающие операции получают признак отмены. После этого старый расчёт не может написать результат в новый экран и вернуть отключённую кнопку старого компонента.

Событие pagehide тоже требует аккуратности. Браузер может сохранить страницу в back-forward cache для быстрого возврата. Если event.persisted равен true, мы не уничтожаем работающий сценарий. Иначе после кнопки «Назад» часть контролов могла бы выглядеть доступной, хотя их слушатели уже удалены. Окончательный уход и временное сохранение страницы — разные ситуации.

const controls = animate(panel, { opacity: 1 });
// Сразу показать конечное состояние:
controls.complete();
// При окончательном удалении компонента:
controls.stop();
// В полном проекте вместе с этим освобождаются слушатели и таймеры.

Уменьшение движения и доступность

Prefers-reduced-motion — запрос пользователя на менее подвижный интерфейс. Он может быть связан с самочувствием, концентрацией или личным предпочтением. Его не стоит превращать в декоративную галочку, которая отключает только один фоновый эффект, оставляя крупные перемещения и отскоки на каждом действии.

В мастерской системный запрос и ручной переключатель объединяются: если активен любой, переходы завершаются сразу, без задержек. При изменении системной настройки открытая страница обновляет поведение. Форма продолжает проверять вход, кнопка меняет счётчик, список переставляется и отчёт считается. Это простой способ проверить, что бизнес-логика не зависит от количества кадров.

Движение не заменяет доступные названия, контраст и клавиатуру. У сообщений есть текстовый статус, у вкладок — роли и выбранное состояние, у формы — пояснение ошибки. При этом один набор ARIA-атрибутов не является сертификатом полной доступности. Проверяйте реальные сценарии с нужными браузерами и вспомогательными технологиями, особенно в сложной панели с несколькими модальными слоями.

const media = matchMedia('(prefers-reduced-motion: reduce)');
const reduced = media.matches || reduceCheckbox.checked;
animate(element, { opacity: 1, y: 0 }, {
  duration: reduced ? 0 : 0.3,
  delay: 0
});

Официальная документация: системное предпочтение уменьшенного движения.

Производительность: сначала измеряем, затем украшаем

Transform и opacity часто подходят для небольших переходов лучше, чем постоянное изменение геометрии. Но конкретная производительность зависит от браузера, количества элементов и других задач страницы. Нельзя обещать одинаковую плавность по одному названию библиотеки. Проверьте интерфейс на устройстве посетителя, а не только на свободном мощном компьютере.

Высота accordion и ширина индикатора всё равно измеряются, потому что они связаны с содержимым. Измерения выполняются около изменения состояния, а не бесконтрольно на каждом кадре. В FLIP сначала собираются старые прямоугольники, затем меняется DOM и читаются новые. Разбросанные чтения и записи размеров внутри большого цикла способны вызвать лишнюю работу layout.

Не добавляйте тяжёлое размытие, огромные тени и бесконечные фоновые анимации только потому, что API это позволяет. Следите за количеством одновременно активных переходов и завершайте старые. Общая цель — дать человеку понятный результат без заметной задержки. Если удаление эффекта делает рабочую операцию быстрее и яснее, такой эффект стоит пересмотреть.

Официальная документация: производительность анимаций.

Три способа забрать код и не перепутать результат

В мастерской доступны оригинальный JS-модуль, автономный HTML и полный ZIP. Модуль показывает фактическую реализацию выбранного сценария. Он импортирует общие функции из core.js и данные из data.js, поэтому не обещает работать в одиночку после произвольного копирования в пустой файл. Полный проект содержит эти зависимости и сборщик.

Автономный HTML встраивает готовую библиотеку, стили, код и выбранные общие настройки. Откройте его напрямую: сетевые запросы не нужны. Это удобный способ проверить идею у коллеги, сохранить опыт или начать собственный прототип. В файл не записываются текущие сообщения, введённые значения формы и изменения списка — сценарий открывается с исходными учебными данными.

ZIP нужен для разработки и публикации. В нём есть исходные модули, готовый public, package-lock, тесты, лицензии и README. Изменять следует src, а не сжатый app.js: следующая сборка перезапишет последний. Для использования внутри кабинета перенесите нужный сценарий и согласуйте его с существующей моделью состояния, а не копируйте всю мастерскую без разбора.

Windows: запустить, изменить и проверить

Распакуйте архив в отдельную папку. Для локального Node-сервера нужен Node.js 24. Готовые браузерные файлы уже находятся в public, поэтому посмотреть мастерскую можно до установки npm-зависимостей. Сервер слушает только loopback; это инструмент первого знакомства, а не публикация на весь Интернет.

В PowerShell выполните команды ниже и откройте указанный адрес. Для остановки нажмите Ctrl+C. Если порт занят, задайте переменную PORT в текущем терминале и используйте другой адрес. Не меняйте системный Node или конфигурацию других проектов только ради одного примера: сначала проверьте текущую версию и способ установки.

После редактирования исходников выполните npm ci, сборку и тесты. Lock-файл нужен для воспроизводимого состава зависимостей. Не обновляйте Motion одновременно с первым запуском скачанного примера: сначала получите исходный результат, затем отдельно проверяйте обновление. При установленном PHP доступен и локальный просмотр через его встроенный сервер, но он не требуется для самих анимаций.

Set-Location C:\projects\motion-project
node --version
node scripts/server.mjs
# http://127.0.0.1:8142
# Для изменения проекта:
npm ci
npm run build
npm test
# Альтернатива при установленном PHP:
# php -S 127.0.0.1:8142 -t public

Linux: те же исходники, отдельная среда запуска

На Linux последовательность сборки такая же. Установите Node.js 24 подходящим для вашего сервера способом, перейдите в каталог проекта и восстановите зависимости из lock. Права процесса должны позволять читать исходники и создавать файлы сборки, но для npm-команд в собственном рабочем каталоге обычно не требуется root.

Если локальный Node-сервер нужен только для просмотра через SSH, сохраните loopback-привязку и используйте туннель. Не открывайте случайный development-порт внешнему миру вместо настройки обычного HTTPS-сайта. При конфликте порта значение PORT задаётся явно. Команды Bash и PowerShell различаются синтаксисом переменных, хотя запускают один код.

Готовой статической странице Node.js на VPS вообще не нужен. Можно собрать public на рабочей машине и передать его на сервер. Это уменьшает число постоянно работающих процессов и упрощает эксплуатацию. Если же сборка выполняется в CI или контейнере, закрепите версию Node, сохраните lock и запускайте те же проверки до публикации.

cd ~/projects/motion-project
node --version
npm ci
npm run build
npm test
PORT=8142 node scripts/server.mjs

VPS: статическая публикация, HTTPS и типы файлов

В рабочем размещении публикуется только каталог public. Корень проекта с тестами, package-lock и служебными скриптами не должен становиться web root. В README приведены передача ZIP через SSH, отдельный каталог выпуска, права чтения и ссылка current. Эти действия выполняются на подготовленном сервере; наличие примера не означает, что внешний VPS уже настроен.

Для Caddy есть готовый site-блок с HTTPS и Basic Auth для закрытого стенда. Замените домен и хеш пароля, сохранив конфигурацию других сайтов. Если мастерская предназначена для открытой статьи, доступ можно намеренно сделать публичным, поскольку данные синтетические. Нельзя переносить такое решение на реальные документы или CRM без отдельной авторизации.

Проверьте MIME JavaScript. Файлы app.js и standalone.js должны приходить как JavaScript, а не как application/octet-stream. У нас используются расширения .js, что помогает обычному хостингу корректно отдавать ресурсы. Заголовок nosniff не следует убирать ради обхода ошибки конфигурации. Подробнее про сам сервер есть материал о Caddy.

motion.example.com {
    root * /srv/motion/current
    basic_auth {
        demo REPLACE_WITH_CADDY_HASH
    }
    encode zstd gzip
    header X-Content-Type-Options nosniff
    file_server
}
caddy hash-password
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://motion.example.com/
curl -u demo -I https://motion.example.com/app.js

Официальная документация: раздача статических файлов Caddy.

Обновление, логи и возврат к предыдущему выпуску

Размещайте новый выпуск рядом со старым. Сначала проверьте сборку и поведение, затем переключайте подготовленную ссылку current. Предыдущий архив и каталог сохраняются до приёмки. Если новый вариант не работает, ссылка возвращается на прежний выпуск. Конкретные команды и проверки путей описаны в README; они не требуют удаления всего проекта или глобальной очистки сервера.

В Caddy полезны access log и журнал службы. Ошибка загрузки исходника часто означает, что не скопирован public/sources. Неудачный автономный экспорт может быть связан с отсутствующим standalone.js или файлом лицензий. Пустая страница требует проверки HTML, JavaScript и консоли, а не повторного добавления второй версии библиотеки через CDN.

При обновлении учитывайте кеш: HTML одного выпуска и JavaScript другого могут создать трудноуловимую ошибку. В учебной конфигурации используется no-cache; в большом проекте удобнее файлы с хешами сборки. Архив source, lock и конфигурация сервера входят в резервирование. Данных посетителей у мастерской нет, но в вашем приложении их восстановление должно планироваться отдельно.

sudo journalctl -u caddy --since '30 minutes ago'
sudo tail -n 40 /var/log/caddy/motion-access.json
# После проверки нового каталога выпуска:
# подготовьте current.next и атомарно замените вашу symlink current.
# Перед заменой сверяйте пути; полный порядок есть в README.

Как встроить эти примеры в существующий сервис

Выберите конкретный пользовательский путь. Для каталога пригодится фильтрация, для диспетчера — приоритеты, для кабинета — вкладки и боковая панель. Не обязательно переносить все девять эффектов сразу. Когда каждое изменение интерфейса сопровождается движением, полезный сигнал превращается в постоянный визуальный шум.

Если состояние уже хранится в Alpine.js, React или другом слое, сохраняйте один источник истины. Motion получает итоговые значения и управляет переходом; второй независимый массив задач внутри анимации быстро расходится с настоящими данными. Для небольших HTML-интерфейсов можно сопоставить подход с мастерской Alpine.js.

При серверном обновлении через PHP или htmx учитывайте замену DOM. Старые слушатели и анимации очищаются, новый фрагмент получает собственный экземпляр компонента. Не храните ссылку на удалённый элемент и не показывайте успех до подтверждения сервера. Анимация должна следовать фактическому результату операции, включая ошибку и повтор.

Что проверять перед использованием на своём сайте

Начните с обычного сценария, затем попробуйте быстрые повторные клики, смену вкладки во время перехода, пустой фильтр и отмену расчёта. Важно проверить не только конечную картинку: интерфейс не должен зависать между состояниями или получать результат от уже закрытого компонента. Сравните экран с выгруженным JSON и исходной моделью данных.

Отдельно пройдите страницу клавиатурой и включите уменьшение движения в ОС. Проверьте узкую область встраивания, а не только большой экран телефона: внутри статьи доступная ширина может быть меньше ширины устройства. В автоматических тестах проекта используется настоящая собранная Motion, включая промежуточную прозрачность и завершение перехода; DOM-окружение дополняет браузерный просмотр, но не заменяет его.

Проверка физического телефона, конкретного скринридера и production-нагрузки относится к вашему конечному приложению. Учебные списки маленькие, данные синтетические, внешние сервисы не вызываются. Начните с одного полезного компонента, свяжите его с реальными правами и состоянием, затем расширяйте набор только там, где движение делает действие понятнее.

Исходники и официальные материалы

Полный проект включает девять сценариев, общий владелец анимаций и cleanup, учебные данные, исходники автономного экспорта, готовые JS/CSS, Node-сервер для просмотра, npm lock, тесты, лицензии и инструкции для Windows/Linux/VPS. Скачиваемый HTML удобен для отдельного опыта; архив подходит для дальнейшей разработки и встраивания.

Перед обновлением библиотеки смотрите официальный API и проверяйте свои сценарии. Версия в статье закреплена намеренно: повторяемый пример полезнее ссылки на неопределённое latest. Сохраняйте простую проверку результата без анимации. Тогда изменение длительности, устройства или способа отрисовки не превращает правильную форму и список в другой по смыслу интерфейс.