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

SortableJS: перетаскивание и 6 интерактивных примеров

План встречи, канбан с лимитом, конструктор формы, меню, Swap и MultiDrag. Перетаскивайте карточки прямо в статье, проверяйте результат и скачивайте исходники.

Александр Некрасов
SortableJS: порядок в интерфейсе, карточки с ручками захвата и стрелкой переноса на тёмно-бирюзовом фоне

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

SortableJS отвечает за сортировку элементов и перенос между списками. Библиотека позволяет задавать ручки захвата, группы списков, клонирование и обработчики событий, а плагины Swap и MultiDrag добавляют обмен позициями и перемещение нескольких элементов, как показано в официальных примерах. Мы используем её внутри собственных прикладных интерфейсов, а не копируем демонстрации без контекста.

Что будем собирать и проверять

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

  • План встречи: меняем порядок и длительность тем, получаем новое расписание.
  • Канбан: переносим задачи, учитываем статусы и ограничение «не больше двух в работе».
  • Конструктор формы: клонируем шаблоны, редактируем подписи и проверяем получившиеся поля.
  • Меню сайта: переставляем и скрываем пункты, видим обновлённую навигацию.
  • Swap: меняем две карточки местами, не сдвигая остальные.
  • MultiDrag: собираем несколько материалов и переносим их вместе.

Живая мастерская SortableJS

Для перетаскивания используйте ручку с точками слева от названия. На телефоне начинайте движение именно с неё, а страницу прокручивайте вне ручки. Если перетаскивание неудобно, стрелки, выбор списка и кнопки добавления выполняют те же прикладные действия без drag-and-drop.

«Отменить» возвращает последнее изменение карточек; история хранит до 30 состояний каждой сцены. «Сбросить пример» восстанавливает исходный набор, причём сброс тоже можно отменить. Кнопка «Развернуть» открывает мастерскую на всё окно без перезагрузки, Escape возвращает обычный размер.

Шесть примеров применения в интерфейсе

План встречи: порядок влияет на время

Перетащите «Вступление» после «Обратной связи». Темы в редакторе и строки расписания поменяют порядок, а начало каждого пункта пересчитается. Измените длительность темы и время начала встречи, чтобы проверить, как это влияет на итог.

В начальном наборе четыре темы общей длительностью 50 минут. Значение каждого пункта можно менять от 1 до 90 минут. Стрелки вверх и вниз дают возможность получить то же расписание с клавиатуры: кнопки недоступного направления отключены.

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

Перестроить расписание встречи

Канбан: перенос с проверкой лимита

Попробуйте перенести задачу в колонку «В работе»: в исходном состоянии там уже две карточки, поэтому мастерская отклонит перенос и объяснит причину. Сначала отправьте одну из текущих задач в «Готово», затем повторите попытку. Счётчики и список статусов под доской обновятся.

Ограничение в две задачи написано в нашем приложении. SortableJS предоставляет событие onMove, из которого можно вернуть false, чтобы запретить перемещение, согласно описанию обработчиков API. Мы также проверяем готовую модель после drop, а кнопочный перенос использует то же правило.

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

Проверить лимит задач

Конструктор формы: клонирование без потери шаблона

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

Клонирование между группами включается через pull: 'clone'; настройка sort: false сохраняет порядок шаблонов, а put: false запрещает обратный перенос в библиотеку. Эти параметры описаны в разделах group и sort. Выдачу нового ID и построение формы мы реализовали отдельно.

Ниже редактора находится настоящая форма с обязательными полями. Заполните её и нажмите «Проверить локально»: браузер проверит поля, но данные никуда не отправятся. Важно: изменение структуры перерисовывает предпросмотр и очищает введённые ответы. Это редактор формы, а не средство сбора заявок.

В нашем примере максимум 12 полей. Кнопка «Добавить поле» заменяет перетаскивание, а отмена позволяет вернуть удалённое. Такой подход можно использовать в редакторе опросов, но в реальном проекте дополнительно понадобятся схема валидации, обработка отправки и сохранение структуры.

Собрать и проверить форму

Меню сайта: порядок и видимость

Перенесите «Контакты» из скрытых пунктов в меню и поднимите этот раздел на нужную позицию. Предпросмотр покажет новый состав и порядок навигации. Нажатие на пункт меняет выбранный учебный раздел, не открывая внешний сайт.

Уберите все видимые пункты, чтобы посмотреть пустое состояние, затем верните один обратно. Если скрыть выбранный пункт, предпросмотр выберет первый оставшийся, а не продолжит показывать несуществующий раздел. Так перетаскивание связано не только с массивом, но и с состоянием интерфейса.

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

Настроить навигацию

Swap: обмен двух позиций

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

Режим использует плагин Swap, включаемый параметром swap: true, и отдельный класс подсветки цели swapClass, как в примере Swap. Здесь стрелки тоже обменивают соседние карточки, а не выполняют другой вид перестановки.

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

Обменять две карточки местами

MultiDrag: перенос выбранного набора

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

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

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

Собрать подборку несколькими карточками

Почему важно обновлять данные, а не только DOM

SortableJS умеет читать порядок через toArray(), используя идентификаторы элементов; метод и атрибут dataIdAttr описаны в документации методов. Но массив задач или схема формы сами по себе от этого не становятся актуальными. Приложение должно сопоставить порядок DOM с моделью, а затем обновить интерфейс и сохранение.

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

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

Подключение и готовые исходники

В этой мастерской закреплена версия 1.15.7. Установить её можно командой npm install sortablejs@1.15.7; варианты подключения и модульные сборки описаны в README проекта. Мы используем complete-сборку, в которой плагины уже подключены, поэтому повторный mount не нужен.

<div id="tasks">
  <article data-id="task-1">
    <span class="grip">⠿</span> Проверить форму
  </article>
  <article data-id="task-2">
    <span class="grip">⠿</span> Подготовить описание
  </article>
</div>
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';

const tasks = [
  { id: 'task-1', title: 'Проверить форму' },
  { id: 'task-2', title: 'Подготовить описание' }
];
let orderedTasks = [...tasks];

const sortable = new Sortable(document.querySelector('#tasks'), {
  draggable: 'article',
  handle: '.grip',
  animation: matchMedia('(prefers-reduced-motion: reduce)').matches
    ? 0 : 150,
  onEnd() {
    const byId = new Map(orderedTasks.map(task => [task.id, task]));
    orderedTasks = sortable.toArray().map(id => byId.get(id));
    console.log(orderedTasks);
    // Здесь обновите предпросмотр и сохраните порядок.
  }
});

Этот фрагмент показывает один список, а не весь интерфейс мастерской. В блоке «Код» внутри каждого сценария находится настройка именно этого режима. Полный ZIP содержит JavaScript, CSS, данные, правила импорта, отмену, живые предпросмотры и README с командами запуска.

Если внутри карточек находятся поля и кнопки, важно не превращать любое нажатие в начало drag. В мастерской захват ограничен handle: '.grip', а интерактивные элементы исключены через filter с preventOnFilter: false; назначение этих параметров описано в API SortableJS. Кнопочные альтернативы перемещения написаны нами отдельно.

Экспорт, импорт и ограничения

«Скачать состояние JSON» сохраняет текущую сцену, списки и карточки. Импорт принимает этот формат размером до 100 000 байт: проверяются версия, сцена, структура списков, уникальность ID, длина названий и ограничения сценария. Некорректный файл не заменяет рабочее состояние.

Проверьте это самостоятельно: скачайте JSON, измените порядок в интерфейсе, затем загрузите файл обратно. После этого попробуйте повторить один ID или поместить третью задачу в «В работе». Мастерская покажет ошибку, а текущие карточки останутся на месте.

Это не универсальный импорт произвольного каталога: кроме конструктора формы, набор ID должен соответствовать исходным карточкам сцены. Дополнительные свойства отбрасываются, подписи выводятся с экранированием. Для другого проекта адаптируйте схему в src/data.js, а не отключайте проверку целиком.

Анимация, блокировка перетаскивания, время начала встречи, выделение MultiDrag и ответы в предпросмотре формы не сохраняются в JSON. Данные не отправляются на сервер, localStorage не используется. После перезагрузки страницы изменения пропадут, если вы не сохранили файл.

Что проверить перед применением в продукте

  • Смысл переноса: пользователь должен понимать, что изменилось: порядок, статус, видимость или принадлежность к списку. Покажите результат, а не только анимацию.
  • Альтернативное управление: оставьте кнопки, выбор списка и видимый фокус. Не делайте перетаскивание единственным способом выполнить действие.
  • Правила: проверяйте лимиты как перед drop, так и при сохранении. Для серверного продукта подтверждайте изменения на сервере.
  • Пустые списки: выделите достаточно большую область drop и объясните, что туда можно перенести. Проверьте сценарий, когда последний элемент покинул колонку.
  • Сенсорный экран: отделите ручку захвата от прокрутки, проверьте длинную страницу и перенос между расположенными друг под другом колонками.
  • История: определите, какие изменения можно отменить и что именно сохраняется. Структура формы и введённые ответы пользователя требуют разных решений.

Начните с подходящей сцены и проверьте её на собственных данных. Полезный drag-and-drop заканчивается не перемещённой карточкой, а согласованным состоянием: редактор, предпросмотр и экспорт должны говорить об одном и том же.