We have the following code sample:
function DraggableText({ x, y, text }) {
const [position, setPosition] = React.useState({ x: x, y: y });
const [isDragging, setIsDragging] = React.useState(false);
const [mouseOffset, setMouseOffset] = React.useState({ x: 0, y: 0 });
const handleMouseDown = (event) => {
event.preventDefault();
setIsDragging(true);
setMouseOffset({
x: event.clientX - position.x,
y: event.clientY - position.y,
});
};
const handleMouseUp = (event) => {
setIsDragging(false);
};
const handleMouseMove = (event) => {
if (isDragging) {
setPosition({
x: event.clientX - mouseOffset.x,
y: event.clientY - mouseOffset.y,
});
}
};
return (
<text
x={position.x}
y={position.y}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
style={{ cursor: 'move' }}
>
{text}
</text>
);
}
function D3BarChart({ }) {
// And Finally, Return!
return (
<svg
className='cbb-box-shadowed'
width='100%'
viewBox={`0 0 700 450`}
preserveAspectRatio='xMaxYMax'
style={{ background: '#F2F2F2' }}
>
<DraggableText x={100} y={100} text="Drag me!" />
</svg>
);
}
// render both components
ReactDOM.render(
(<div>
<div width='60%'>
<D3BarChart />
</div>
</div>),
document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="root">React component will be rendered here.</div>
When we click to drag the text, the mouse moves about 5% faster than the text in each direction. The result is a buggy experience using the text-drag.
The issue appears to be related to the viewBox
in the <svg>
. We are looking for a solution that keeps the viewBox
and the width: 100%
as is on the SVG, as well as the <div width='60%'>
that the D3BarChart
is returned inside of, as these are key parts to how the graphs are created and displayed on our web application.
Perhaps there is a way to offset the viewBox
value in the <DraggableText />
?
3
Answers
Try this code.
Then main problem with your code, as you already said, is that the svg width and the svg viewBox width are different.
In your case the svg width is 100%, so it’s dinamically calculated, but its viewBox width is 700px. If you set both to the same value you would avoid this issue, otherwise you have to do some math.
For sake of simplicity, let’s say you have this markup:
<svg width="300px" viewBox="0 0 600 …
. Your svg would occupy 300px on the screen. But since your svg viewBox width is 600px, the svg would "think" to be twice as big when it comes to calculate the position of its internal elements.This means that if you want to position an element into the svg at (100px, 100px) according to the screen geometry, you will need to multiply those values by 2 and set your svg element position to (200px, 200px).
This scaling factor (2 in this example) can be calculated programmatically reading the
SVG.viewBox
attribute and getting the actual current image size on screen withElement.getBoundingClientRect()
, like this:In your code you should scale the mouse position to match the svg geometry, like so:
Apart from this, as others pointed out, you would get a better result using the mouse move event of the svg instead of the text element, since if you drag it around fast enough, your mouse would be able to leave it, and the event will not fire anymore.
Basically the changes I can see compared to my own component (drag handles on a panel) are
useCallback()
for handlers, to rebuild only on dependency changesGiving mousemove and mouseup wider scope allows the text to "catch up" with the mouse if you move it quicker than React can process the events.
Otherwise the mouse can get dissociated from the text and
mousemove
is no longer processed.I also set
position
on mousedown which does away withmouseOffset
state, but haven’t made that change here in case there’s something else going on that I didn’t notice.It would be a small optimization anyway.
Update
I notice when the snippet is opened in full-page mode, it’s no longer tracking properly.
This is due to
scale
being set on component mount, but if you perform this sequencedrag and mouse up
resize the window (in snippet, select "full page")
drag again
then
scale
is wrong for the 2nd drag.The code is now updated to handle window resize.