Getting Started with the DraggablePremium
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:
- onPress—A callback indicating that the
draggableitem has been pressed, but not dragged yet. - onDragStart—A callback indicating that the
draggableitem has been dragged for the first time. - onDrag—A callback called on every
pointer/touchaction, indicating that a drag is happening. - onDragEnd—A callback indicating that the
draggableitem is no longer being dragged. - onRelease—A callback indicating that the
draggableitem has been released.
Basic Usage
The following example demonstrates the draggable functionality in action.
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
useDraggablehook or theDraggablecomponent. - Pass a custom React component to the
useDraggablehook or theDraggablecomponent.
Native HTML Element
- To pass a native HTML Element to the
useDraggablehook, obtain therefobject by using theReact.useRefhook.
You can use the same
refobject 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
Draggablecomponent, 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:
-
To obtain the
ref, wrap the component inReact.forwardRef.jsxconst CustomComponent = React.forwardRef((props, ref) => { return <div ref={ref}>DragMe</div>; });Alternatively, if you are already using the
refand applying additional properties, add theelementfield to theref, which will resolve to the HTML element underneath.jsxconst CustomComponent = React.forwardRef((props, ref) => { const element = React.useRef(); React.useImperativeHandle(ref, () => ({ element: element.current })); return <div ref={element}>DragMe</div>; }); -
Obtain the
refof yourCustomComponentand pass it to theuseDraggablehook.jsxconst component = React.useRef(); // ... useDraggable(component, {}); // ... return <CustomComponent ref={component}>Drag Me</CustomComponent>;
-
-
If the Component is a Class Component:
-
Provide a public
elementgetter as a class field of the component.jsxclass CustomComponent extends React.Component { _element = React.createRef(); get element() { return this._element; } render() { return <div ref={this._element}>DragMe</div>; } } -
Wrap your
CustomComponentinto aDraggable.jsxrender() { 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/clientY—Theclientcoordinates where thedraggingbegins. - The current
clientX/clientY—Theclientcoordinates of the current positioning of thepointer. - The
offsetX/offsetY—Theoffsetcoordinates indicating where thepointerwas positioned relative to thedrag. - The
scrollX/scrollY—Thescrollcoordinates required to account for the current scroll position of the parent.
To position the drag relative to its parent:
-
Set the initial
offsetXandoffsetYvalues by handling theonPresscallback.jsxconst handlePress = (event) => { setOffset({ x: event.offsetX, y: event.offsetY }); }; -
Set the initial
clientXandclientYvalues by handling theonDragStartcallback.jsxconst handleDragStart = (event) => { setInitial({ x: event.clientX - offset.x, y: event.clientY - offset.y }); }; -
Calculate the current position of the
dragby handling theonDragcallback.jsxconst handleDrag = (event) => { setTranslate({ x: event.clientX - initial.x - offset.x + event.scrollX, y: event.clientY - initial.y - offset.y + event.scrollY }); }; -
Finally, apply the
transformstyle to the element.jsxreturn ( <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/clientY—Theclientcoordinates where thedraggingbegins. - The current
clientX/clientY—Theclientcoordinates of the current positioning of thepointer. - The offsetX/offsetY—The
offsetcoordinates indicating where thepointerwas positioned relative to thedrag.
Due to the
dragbeing positionedabsolutelywithin thebodyelement, there is no need to account for thescrollcoordinates of the parent.
To position the drag element absolutely:
-
Set the initial
offsetXandoffsetYvalues by handling theonPresscallback.jsxconst handlePress = (event) => { setOffset({ x: event.offsetX, y: event.offsetY }); }; -
Set the initial
clientXandclientYvalues by handling theonDragStartcallback.jsxconst handleDragStart = (event) => { setInitial({ x: event.clientX - offset.x, y: event.clientY - offset.y }); }; -
Calculate the current position of the
dragby handling theonDragcallback.jsxconst handleDrag = (event) => { setCoordinates({ x: event.clientX - initial.x - offset.x, y: event.clientY - initial.y - offset.y }); }; -
Finally, apply the
transformstyle to the element.jsxreturn ReactDOM.createPortal( <div style={{ position: 'absolute', left: coordinates.x, top: coordinates.y }} > DragMe </div>, document.body );