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 useDraggable hook, obtain the ref object by using the React.useRef hook.
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<HTMLDivElement>(null);
// ...
useDraggable(element, {});
// ...
return <div ref={element}>Drag Me</div>;
The Draggable component will obtain the ref of the child component. If you require a reference, use the childRef.
const element = React.useRef<HTMLDivElement>(null);
// ...
React.useEffect(() => {
// Sample code
element.current?.focus();
}, []);
// ...
return (
<Draggable childRef={element}>
<div>Drag Me</div>
</Draggable>
);
Custom React Component
To pass a custom Functional Component to the useDraggable hook:
-
Wrap the component in
React.forwardRefto obtain theref.tsxconst CustomComponent = React.forwardRef<HTMLDivElement>((props, ref) => { return <div ref={ref}>Drag Me</div>; });Alternatively, if you are already using the
refand applying additional properties, add theelementfield to theref, which will resolve to the HTML element underneath.tsxconst CustomComponent = React.forwardRef<{ element: HTMLDivElement | null }>((props, ref) => { const element = React.useRef<HTMLDivElement>(null); React.useImperativeHandle(ref, () => ({ element: element.current })); return <div ref={element}>Drag Me</div>; }); -
Obtain the
refof yourCustomComponentand pass it to theuseDraggablehook.tsxconst component = React.useRef<HTMLDivElement>(null); // ... useDraggable(component, {}); // ... return <CustomComponent ref={component}>Drag Me</CustomComponent>;
Alternatively, you can wrap your CustomComponent into a Draggable component.
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.tsxconst handlePress = (event: NormalizedDragEvent) => { setOffset({ x: event.offsetX, y: event.offsetY }); }; -
Set the initial
clientXandclientYvalues by handling theonDragStartcallback.tsxconst handleDragStart = (event: NormalizedDragEvent) => { setInitial({ x: event.clientX - offset.x, y: event.clientY - offset.y }); }; -
Calculate the current position of the
dragby handling theonDragcallback.tsxconst handleDrag = (event: NormalizedDragEvent) => { 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.tsxreturn ( <div style={{ transform: `translate(${translate.x}px, ${translate.y}px)` }} > Drag Me </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.tsxconst handlePress = (event: NormalizedDragEvent) => { setOffset({ x: event.offsetX, y: event.offsetY }); }; -
Set the initial
clientXandclientYvalues by handling theonDragStartcallback.tsxconst handleDragStart = (event: NormalizedDragEvent) => { setInitial({ x: event.clientX - offset.x, y: event.clientY - offset.y }); }; -
Calculate the current position of the
dragby handling theonDragcallback.tsxconst handleDrag = (event: NormalizedDragEvent) => { setCoordinates({ x: event.clientX - initial.x - offset.x, y: event.clientY - initial.y - offset.y }); }; -
Finally, apply the
transformstyle to the element.tsxreturn ReactDOM.createPortal( <div style={{ position: 'absolute', left: coordinates.x, top: coordinates.y }} > Drag Me </div>, document.body );