Swappable

Swappable is a lightweight and performant JavaScript library for creating interactive, draggable, and swappable grid layouts. Built with modern web APIs, it offers smooth animations and a simple API for common use cases.

Features

  • Touch-first Draggable Items: Seamless drag-and-drop functionality on both desktop and mobile devices.
  • Smooth Animations: Utilizes the FLIP (First, Last, Invert, Play) animation technique for fluid layout transitions after a swap or item change.
  • CSS-driven Layout: Leverages CSS Grid for a flexible and responsive layout.
  • Lightweight & Performant: No external dependencies, ensuring a small footprint.
  • Customizable: Easily configure class names, animation durations, and drag behavior.
    codepen
    demo

swappable swappable swappable swappable swappable

Installation

$ npm i swappable
import Swappable from "swappable";
import "./node_modules/swappable/dist/swappable.css";

Usage

Basic Setup

Create a container with grid items and initialize Swappable:

<div id="grid" class="swappable-grid">
  <div class="grid-item">Item 1</div>
  <div class="grid-item">Item 2</div>
  <div class="grid-item">Item 3</div>
  <div class="grid-item">Item 4</div>
</div>

<script type="module">
  import Swappable from "swappable";

  const grid = new Swappable("#grid", {
    itemsPerRow: 4,
    dragHandle: ".grid-item",
    layoutDuration: 300,
    swapDuration: 300,
    longPressDelay: 100,
  });
</script>

Options

Customize Swappable with the following options:

Option Type Default Description
dragEnabled boolean true Enable or disable dragging.
dragHandle string | null ".grid-item" Selector for drag handle (null for entire item).
classNames ClassNames See below Custom class names for grid items and states.
layoutDuration number 300 Duration of layout animations (ms).
swapDuration number 300 Duration of swap animations (ms).
layoutEasing string "ease" Easing function for animations.
itemsPerRow number 4 Number of items per row in the grid.
longPressDelay number 100 Delay (ms) for initiating drag on touch devices.

Default classNames:

{
  "item": "grid-item",
  "drag": "dragging",
  "placeholder": "placeholder",
  "ghost": "ghost",
  "hidden": "hidden"
}

Events

Swappable supports the following events:

Event Data Type Description
add { items: HTMLElement[] } Fired when items are added.
remove { items: HTMLElement[] } Fired when items are removed.
dragStart { item: HTMLElement, event: PointerEvent } Fired when dragging starts.
dragMove { item: HTMLElement, event: PointerEvent } Fired during dragging.
swap { fromIndex: number, toIndex: number, fromElement: HTMLElement, toElement: HTMLElement } Fired when items are swapped.
sort { oldIndex: number, newIndex: number, items: SwappableItemData[] } Fired after sorting items.
dragEnd { item: HTMLElement, event: PointerEvent } Fired when dragging ends.
layoutStart void Fired before layout animation starts.
layoutEnd void Fired after layout animation ends.

Example of event handling:

grid.on("dragStart", ({ item, event }) => {
  console.log("Dragging started on", item);
});

grid.on("swap", ({ fromIndex, toIndex }) => {
  console.log(`Swapped item from ${fromIndex} to ${toIndex}`);
});

Methods

Method Parameters Description
on event: keyof SwappableEvents, callback Attach an event listener.
off event: keyof SwappableEvents Remove an event listener.
layout duration?: number Recompute and animate layout.
add element: HTMLElement, index?: number Add a new item to the grid.
remove target: HTMLElement | number Remove an item by element or index.
select target: HTMLElement | number Select an item by element or index.
swap fromIndex: number, toIndex: number Swap two items by index.
refresh None Refresh the grid layout.
destroy None Destroy the instance and clean up.
detach None Detach event listeners.
enable None Enable dragging.
disable None Disable dragging.

Example

A complete example with a 4x2 grid and event logging:

<!DOCTYPE html>
<html>
  <head>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/swappable/dist/swappable.css"
    >
    <style>
      .grid-item {
        background: #f0f0f0;
        padding: 20px;
        border: 1px solid #ccc;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="grid" class="swappable-grid">
      <div class="grid-item">Item 1</div>
      <div class="grid-item">Item 2</div>
      <div class="grid-item">Item 3</div>
      <div class="grid-item">Item 4</div>
      <div class="grid-item">Item 5</div>
      <div class="grid-item">Item 6</div>
      <div class="grid-item">Item 7</div>
      <div class="grid-item">Item 8</div>
    </div>

    <script type="module">
      import Swappable from "https://cdn.jsdelivr.net/npm/swappable/dist/index.umd.js";

      const grid = new Swappable("#grid", {
        itemsPerRow: 4,
        longPressDelay: 200,
      });

      grid.on("swap", ({ fromIndex, toIndex }) => {
        console.log(`Swapped item from ${fromIndex} to ${toIndex}`);
      });

      grid.on("layoutEnd", () => {
        console.log("Layout animation completed");
      });
    </script>
  </body>
</html>

Plugins

import Swappable, { withAutoResponsiveLayout, withHistory } from "swappable";

let swappableInstance = new Swappable("#my-grid");

// with the history plugin
let swappableWithHistory = withHistory(swappableInstance, {
  maxHistorySize: 20,
});

// with the responsive layout plugin
let finalSwappable = withAutoResponsiveLayout(swappableWithHistory, {
  breakpoints: [
    { breakpoint: 0, items: 1 }, // 1 column for smallest screens
    { breakpoint: 576, items: 2 }, // 2 columns for screens >= 576px
    { breakpoint: 768, items: 3 }, // 3 columns for screens >= 768px
    { breakpoint: 992, items: 4 }, // 4 columns for screens >= 992px
  ],
});

Contributing

We welcome contributions! Feel free to open issues or pull requests on the GitHub repository.


License

This project is licensed under the MIT License.

dnddragdrag-and-dropdrag-dropdraggablelightweighttiny