Skip to content

Instantly share code, notes, and snippets.

@hashrock
Last active January 31, 2024 13:22
Show Gist options
  • Save hashrock/0e8f10d9a233127c5e33b09ca6883ff4 to your computer and use it in GitHub Desktop.
Save hashrock/0e8f10d9a233127c5e33b09ca6883ff4 to your computer and use it in GitHub Desktop.
SVG Drag and Drop with React Hooks
import React from "react";
import ReactDOM from "react-dom";
const Circle = () => {
const [position, setPosition] = React.useState({
x: 100,
y: 100,
active: false,
offset: { }
});
const handlePointerDown = e => {
const el = e.target;
const bbox = e.target.getBoundingClientRect();
const x = e.clientX - bbox.left;
const y = e.clientY - bbox.top;
el.setPointerCapture(e.pointerId);
setPosition({
...position,
active: true,
offset: {
x,
y
}
});
};
const handlePointerMove = e => {
const bbox = e.target.getBoundingClientRect();
const x = e.clientX - bbox.left;
const y = e.clientY - bbox.top;
if (position.active) {
setPosition({
...position,
x: position.x - (position.offset.x - x),
y: position.y - (position.offset.y - y)
});
}
};
const handlePointerUp = e => {
setPosition({
...position,
active: false
});
};
return (
<circle
cx={position.x}
cy={position.y}
r={50}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerMove={handlePointerMove}
fill={position.active ? "blue" : "black"}
/>
);
};
function App() {
return (
<svg viewBox="0 0 400 400" width="400" height="400">
<Circle />
</svg>
);
}
const Application = () => {
return (
<div>
<h1>Drag Me</h1>
<App />
</div>
);
};
ReactDOM.render(<Application />, document.getElementById("app"));
@hashrock
Copy link
Author

hashrock commented Jan 31, 2024

@Minious
That's right, when you rearrange elements during a drag operation, you lose the reference to the DOM.
The rearrangement should be done when the dragging is finished.

If it were me, I would probably just render the object coming to the forefront twice.

  return (
    <svg
      width={800}
      height={800}
      viewBox="-100 -100 400 400"
      style={{
        backgroundColor: "#ff0000",
        position: "absolute",
        left: 50,
        top: 50,
      }}
    >
      {rectElements}
      {elements.filter((item) => item.active === true).map((item) => {
        return (
          <rect
            key="active"
            x={item.x}
            y={item.y}
            fill="yellow"
            stroke="white"
            width={item.width}
            height={item.height}
            style={{ pointerEvents: "none" }}
          />
        );
      }
      )}
    </svg>
  );

I often use techniques to create a UI layer that is just for appearance and disconnected from the actual contents. By using pointer-events: none, it is possible to make events transparent.

image

https://dev.to/hashrock/writing-spreadsheet-with-svg-and-vuejs--23ed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment