Getting Started with the Draggable

The useDraggable hook and Draggable component enable the user to drag a native HTML Element or custom React Component.

The draggable functionality provides the following callbacks which enable the developer to implement custom positioning, styling, or behavior based on the current application state and the event arguments:

  • onPressA callback indicating that the draggable item has been pressed, but not dragged yet.
  • onDragStartA callback indicating that the draggable item has been dragged for the first time.
  • onDragA callback called on every pointer/touch action, indicating that a drag is happening.
  • onDragEndA callback indicating that the draggable item is no longer being dragged.
  • onReleaseA callback indicating that the draggable item has been released.

Basic Usage

The following example demonstrates the draggable functionality in action.

Example
View Source
Change Theme:

Registering for Drag

To register an element/component to the useDraggable hook, pass it as the first parameter. For the Draggable component, it is enough to provide a single children prop to the component.

In the following section, you will learn how to:

  • Pass a native HTML element to the useDraggable hook or the Draggable component.
  • Pass a custom React component to the useDraggable hook or the Draggable component.

Native HTML Element

  • To pass a native HTML Element to the useDraggable hook, obtain the ref object by using the React.useRef hook.

You can use the same ref object for other parts of your application, as long as you do not mutate it directly.

    const element = React.useRef();
    // ...
    useDraggable(element, {});
    // ...
    return (
        <div ref={element}>Drag Me</div>
    )
  • To pass a native HTML Element to the Draggable component, pass the desired element as a direct child.
    render() {
        return (
            <Draggable>
                <div>Drag Me</div>
            </Draggable>
        )
    }

The Draggable component will obtain the ref of the child component. If you require a reference, use the childRef.

    element = React.createRef();
    // ...
    componentDidMount() {
        // Sample code
        element.current.focus();
    }
    // ...
    render() {
        return (
            <Draggable childRef={this.element}>
                <div>Drag Me</div>
            </Draggable>
        )
    }

Custom React Component

There are two possible ways to pass a custom React Component to the useDraggable hook:

  • If the Component is a Functional Component:

    1. To obtain the ref, wrap the component in React.forwardRef.

          const CustomComponent = React.forwardRef((props, ref) => {
              return (
                  <div ref={ref}>DragMe</div>
              )
          });

      Alternatively, if you are already using the ref and applying additional properties, add the element field to the ref, which will resolve to the HTML element underneath.

          const CustomComponent = React.forwardRef((props, ref) => {
              const element = React.useRef();
      
              React.useImperativeHandle(ref, () => ({
                  element: element.current
              }))
      
              return (
                  <div ref={element}>DragMe</div>
              )
          });
    2. Obtain the ref of your CustomComponent and pass it to the useDraggable hook.

          const component = React.useRef();
          // ...
          useDraggable(component, {});
          // ...
          return (
              <CustomComponent ref={component}>Drag Me</CustomComponent>
          )
  • If the Component is a Class Component:

    1. Provide a public element getter as a class field of the component.

          class CustomComponent extends React.Component {
              _element = React.createRef();
      
              get element() {
                  return this._element;
              }
      
              render() {
                  return (
                      <div ref={this._element}>DragMe</div>
                  )
              }
          }
    2. Wrap your CustomComponent into a Draggable.

          render() {
              return (
                  <Draggable>
                      <CustomComponent>Drag Me</CustomComponent>
                  </Draggable>
              )
          }

Handling Drag Events

The KendoReact drag callbacks provide the necessary information to apply correct positioning on the page. The drag can be positioned relatively to its parent or absolutely in the document body.

Relative Positioning

To calculate the drag position correctly, you need:

  • The initial clientX/clientYThe client coordinates where the dragging begins.
  • The current clientX/clientYThe client coordinates of the current positioning of the pointer.
  • The offsetX/offsetYThe offset coordinates indicating where the pointer was positioned relative to the drag.
  • The scrollX/scrollYThe scroll coordinates required to account for the current scroll position of the parent.

To position the drag relative to its parent:

  1. Set the initial offsetX and offsetY values by handling the onPress callback.

    const handlePress = (event) => {
        setOffset({x: event.offsetX, y: event.offsetY});
    };
  2. Set the initial clientX and clientY values by handling the onDragStart callback.

    const handleDragStart = (event) => {
        setInitial({x: event.clientX - offset.x, y: event.clientY - offset.y});
    };
  3. Calculate the current position of the drag by handling the onDrag callback.

    const handleDrag = (event) => {
        setTranslate({
            x: event.clientX - initial.x - offset.x + event.scrollX,
            y: event.clientY - initial.y - offset.y + event.scrollY
        })
    };
  4. Finally, apply the transform style to the element.

    return (
        <div
            style={{
                transform: `translate(${translate.x}px, ${translate.y}px)`;
            }}
        >
            DragMe
        </div>
    )

Absolute Positioning

Absolute positioning is most-suitable for scenarios where the drag might happen between two different parent elements.

To calculate the drag position correctly, you need:

  • The initial clientX/clientYThe client coordinates where the dragging begins.
  • The current clientX/clientYThe client coordinates of the current positioning of the pointer.
  • The offsetX/offsetYThe offset coordinates indicating where the pointer was positioned relative to the drag.

Due to the drag being positioned absolutely within the body element, there is no need to account for the scroll coordinates of the parent.

To position the drag element absolutely:

  1. Set the initial offsetX and offsetY values by handling the onPress callback.

    const handlePress = (event) => {
        setOffset({x: event.offsetX, y: event.offsetY});
    };
  2. Set the initial clientX and clientY values by handling the onDragStart callback.

    const handleDragStart = (event) => {
        setInitial({x: event.clientX - offset.x, y: event.clientY - offset.y});
    };
  3. Calculate the current position of the drag by handling the onDrag callback.

    const handleDrag = (event) => {
        setCoordinates({
            x: event.clientX - initial.x - offset.x,
            y: event.clientY - initial.y - offset.y
        })
    };
  4. Finally, apply the transform style to the element.

    return ReactDOM.createPortal((
        <div
            style={{
                position: "absolute",
                left: coordinates.x,
                top: coordinates.y
            }}
        >
            DragMe
        </div>
    ), document.body)