Reputation: 1077
I'm mainly a backend engineer and have been trying to implement, and failing, a simple drag and drop for a slider I am making in React.
First I will show you the behavior without using debounce: no-debounce
And here is the behavior with debounce: with-debounce
The debounce I took from here with a little modification.
I think I have two problems, one is fast flickering, which debounce should solve, and the other is the incorrect left
which I cannot figure how to fix. For some reason, onDrag, rect.left has all the left margins of the parents (100 + 10) added to it as well. This happens on Chrome and Safari.
My question is, how do I make this drag and drop work? What am I doing wrong? My code is below:
import React, {
Dispatch,
MouseEvent,
RefObject,
SetStateAction,
useEffect,
useRef,
useState
} from "react";
const useDebounce = (callback: any, delay: number) => {
const latestCallback = useRef(callback);
const latestTimeout = useRef(1);
useEffect(() => {
latestCallback.current = callback;
}, [callback]);
return (obj: any) => {
const { event, args } = obj;
event.persist();
if (latestTimeout.current) {
clearTimeout(latestTimeout.current);
}
latestTimeout.current = window.setTimeout(
() => latestCallback.current(event, ...args),
delay
);
};
};
const setPosition = (
event: any,
setter: any
) => {
const rect = event.target.getBoundingClientRect();
const clientX: number = event.pageX;
console.log('clientX: ', clientX)
// console.log(rect.left)
setter(clientX - rect.left)
};
const Slider: React.FC = () => {
const [x, setX] = useState(null);
const handleOnDrag = useDebounce(setPosition, 100)
return (
<div style={{ position: "absolute", margin: '10px' }}>
<div
style={{ position: "absolute", left: "0", top: "0" }}
onDragOver={e => e.preventDefault()}
>
<svg width="300" height="10">
<rect
y="5"
width="300"
height="2"
rx="10"
ry="10"
style={{ fill: "rgb(96,125,139)" }}
/>
</svg>
</div>
<div
draggable={true}
onDrag={event => handleOnDrag({event, args: [setX]})}
// onDrag={event => setPosition(event, setX)}
style={{
position: "absolute",
left: (x || 0).toString() + "px",
top: "5px",
width: "10px",
height: "10px",
padding: "0px",
}}
>
<svg width="10" height="10" style={{display:"block"}}>
<circle cx="5" cy="5" r="4" />
</svg>
</div>
</div>
);
};
Thank you.
Upvotes: 1
Views: 3428
Reputation: 4862
Draggable and onDrag hast its own woes. Tried my hand with simple mouse events.
You can find a working code in the following sandbox
The source for it is as follows
import React, { useRef, useState, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const isDragging = useRef(false);
const dragHeadRef = useRef();
const [position, setPosition] = useState(0);
const onMouseDown = useCallback(e => {
if (dragHeadRef.current && dragHeadRef.current.contains(e.target)) {
isDragging.current = true;
}
}, []);
const onMouseUp = useCallback(() => {
if (isDragging.current) {
isDragging.current = false;
}
}, []);
const onMouseMove = useCallback(e => {
if (isDragging.current) {
setPosition(position => position + e.movementX);
}
}, []);
useEffect(() => {
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
return () => {
document.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mousemove", onMouseMove);
};
}, [onMouseMove, onMouseDown, onMouseUp]);
return (
<div
style={{
flex: "1",
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh"
}}
>
<div
style={{
height: "5px",
width: "500px",
background: "black",
position: "absolute"
}}
>
<div
ref={dragHeadRef}
style={{
left: `${position}px`,
transition: 'left 0.1s ease-out',
top: "-12.5px",
position: "relative",
height: "30px",
width: "30px",
background: "black",
borderRadius: "50%"
}}
/>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The crux of the logic is e.movementX
which returns the amount of distance moved along x axis by the mouse since the last occurrence of that event. Use that to set the left position of the dragHeader
Upvotes: 2