Make Drag & Drop + DropZone with interact.js + reactjs

0xkoji - Nov 1 '22 - - Dev Community

What is interact.js

https://interactjs.io/

JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+).JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+).JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+).

GitHub logo taye / interact.js

JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+)

interact.js

JavaScript drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+)

Gitter jsDelivr Build Status

Features include:

  • inertia and snapping
  • multi-touch, simultaneous interactions
  • cross browser and device, supporting the desktop and mobile versions of Chrome, Firefox and Opera as well as Internet Explorer 9+
  • interaction with SVG elements
  • being standalone and customizable
  • not modifying the DOM except to change the cursor (but you can disable that)

Installation

  • npm: npm install interactjs
  • jsDelivr CDN: <script src="https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js"></script>
  • unpkg CDN: <script src="https://unpkg.com/interactjs/dist/interact.min.js"></script>
  • Rails 5.1+:
    1. yarn add interactjs
    2. //= require interactjs/interact
  • Webjars SBT/Play 2: libraryDependencies ++= Seq("org.webjars.npm" % "interactjs" % version)

Typescript definitions

The project is written in Typescript and the npm package includes the type definitions, but if you need the typings alone, you can install them with:

npm install --save-dev @interactjs/types

Documentation

http://interactjs.io/docs

Example

var pixelSize = 16;
interact('.rainbow-pixel-canvas')
Enter fullscreen mode Exit fullscreen mode

Recently I've worked on Electron + reactjs for h/w(hardware). Then I needed to implement gestures such as long press, swipe, scroll up/down, and drag & drop.

Actually, I thought if I used hammerjs, it would be easy, but actually I gave up using that since it seemed that hammerjs's development wasn't active any more unfortunately.

The last tag was 2016, for personal development that would be fine but the project isn't a personal project so I gave up using that. Then I was doing some research on Openbase.

I checked a few libraries and created a compare list to get feedback from team members. Then we decided to use interact.js because the number of starts is good and it supports gestures the design team requested. In addition, its document is well-organized and we can use it with typescript easily.

docs
https://interactjs.io/docs/

In this article, I will introduce dropzone ui with interact.js + react hooks.

useDrag.ts
https://interactjs.io/docs/draggable/

If you want to use vanillajs, you can use the sample code directly.

import React from "react";
import interact from "interactjs";

type Partial<T> = {
  [P in keyof T]?: T[P];
};

const initialPosition = {
  width: 100,
  height: 100,
  x: 0,
  y: 225
};

export const useDraggable = (
  position: Partial<typeof initialPosition> = initialPosition
) => {
  const [elementPosition, setElementPosition] = React.useState<
    typeof initialPosition
  >({
    ...initialPosition,
    ...position
  });

  const [isEnabled, setIsEnabled] = React.useState<boolean>(true);

  const interactiveRef = React.useRef(null);

  let { x, y, width, height } = elementPosition;

  const enable = () => {
    interact((interactiveRef.current as unknown) as HTMLElement)
      .draggable({
        modifiers: [],
        inertia: false
      })
      .on("dragmove", (event) => {
        x += event.dx;
        y += event.dy;

        setElementPosition({
          width,
          height,
          x,
          y
        });
      });
  };

  const disable = () => {
    interact((interactiveRef.current as unknown) as HTMLElement).unset();
  };

  React.useEffect(() => {
    if (isEnabled) {
      enable();
    } else {
      disable();
    }
    return disable;
  }, [isEnabled]);

  return {
    ref: interactiveRef,
    style: {
      transform: `translate3D(${elementPosition.x}px, ${elementPosition.y}px, 0)`,
      width: `${elementPosition.width}px`,
      height: `${elementPosition.height}px`,
      position: "absolute" as React.CSSProperties["position"],
      touchAction: "none"
    },
    position: elementPosition,
    isEnabled,
    enable: () => setIsEnabled(true),
    disable: () => setIsEnabled(false)
  };
};
Enter fullscreen mode Exit fullscreen mode

App.tsx
https://interactjs.io/docs/dropzone/

import { useRef } from "react";
import interact from "interactjs";
import "./styles.css";
import { useDraggable } from "./hooks/useDraggable";

export default function App() {
  const targetRef = useRef(null);
  const draggable = useDraggable();

  // dropzone
  if (targetRef?.current) {
    interact((targetRef.current as unknown) as HTMLElement)
      .dropzone({
        accept: ".test",
        overlap: 0.75
      })

      .on("dropactivate", (event: Interact.InteractEvent) => {
        event.target.classList.add("drop-active");
      })

      .on("dragenter", (event: Interact.InteractEvent) => {
        console.log("dragenter");
        const draggableElement = event.relatedTarget;
        const dropzoneElement = event.target;
        dropzoneElement.classList.add("drop-target");
        draggableElement?.classList.add("can-drop");
        if (draggableElement) {
          draggableElement.style.color = "#fff";
          draggableElement.textContent = "release me";
        }
      })

      .on("dragleave", (event: Interact.InteractEvent) => {
        const draggableElement = event.relatedTarget;
        const dropzoneElement = event.target;
        dropzoneElement.classList.remove("drop-target");
        draggableElement?.classList.remove("can-drop");
        if (draggableElement) {
          draggableElement.textContent = "dragging me";
        }
      })

      .on("drop", (event: Interact.InteractEvent) => {
        const draggableElement = event.relatedTarget;
        if (draggableElement) {
          draggableElement.style.color = "#fff";
          draggableElement.textContent = "hello world";
        }
      })

      .on("dropdeactivate", (event: Interact.InteractEvent) => {
        event.target.classList.remove("drop-active");
        event.target.classList.remove("drop-target");
      });
  }

  return (
    <div className="App">
      <h1>interactjs samples</h1>
      <div className="test" ref={draggable.ref} style={draggable.style}>
        <p>drag me</p>
        <p>
          {`x: ${draggable.position.x.toFixed(0)}`}
          {`y: ${draggable.position.y.toFixed(0)}`}
        </p>
      </div>
      <div className="dropzone" ref={targetRef}>
        dropzone
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .