SVG 中的 Draggable <text> 有问题,光标移动速度比拖动的文本快

Can*_*ice 2 svg drag-and-drop reactjs react-hooks

我们有以下代码示例:

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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

当我们点击拖动文本时,鼠标在各个方向上的移动速度都比文本快5%左右。结果是使用文本拖动时出现错误的体验。

viewBox该问题似乎与中的有关<svg>。我们正在寻找一种解决方案,可以保留SVG 上的viewBoxwidth: 100%,以及<div width='60%'>内部D3BarChart返回的 ,因为这些是如何在我们的 Web 应用程序上创建和显示图形的关键部分。

也许有一种方法可以抵消?viewBox中的值。<DraggableText />

use*_*029 13

基本上,与我自己的组件(面板上的拖动手柄)相比,我可以看到的变化是

  • 将 mousemove 和 mouseup 添加到窗口(或容器),而不是元素
  • 将事件坐标缩放到父级
  • 用于useCallback()处理程序,仅根据依赖项更改进行重建

如果您移动鼠标的速度比 React 处理事件的速度快,则给予 mousemove 和 mouseup 更宽的范围可以让文本“赶上”鼠标。

否则,鼠标可能会与文本分离并且mousemove不再被处理。

function DraggableText({ x, y, text }) {
  const [position, setPosition] = React.useState({ x, y })
  const [isDragging, setIsDragging] = React.useState(false)
  const [mouseOffset, setMouseOffset] = React.useState({ x: 0, y: 0 })

  const textRef = React.useRef()
  const [scale, setScale] = React.useState({ x: 1, y: 1 })

  const calcScale = React.useCallback(() => {
    if (!textRef.current) return
    const parent = textRef.current.parentElement 
    const viewbox = parent.viewBox.baseVal;
    const rect = parent.getBoundingClientRect();
    const scale = {
      x: viewbox.width / Math.round(rect.width), 
      y: viewbox.height / Math.round(rect.height) 
    }
    return scale
  }, [])

  const handleMouseDown = React.useCallback(event => {
    event.preventDefault()
    setIsDragging(true)

    const scale = calcScale()
    setScale(scale)

    const newPosition = {
      x: (event.clientX * scale.x) - position.x,
      y: (event.clientY * scale.y) - position.y,
    }
    setMouseOffset(newPosition)
  }, [position, calcScale, scale])

  const handleMouseUp = React.useCallback(() => {
    if (!isDragging) return
    setIsDragging(false)
  }, [isDragging])

  const handleMouseMove = React.useCallback(event => {
    if (!isDragging) return
    setPosition({
      x: scale.x * event.clientX - mouseOffset.x,
      y: scale.y * event.clientY - mouseOffset.y,
    })
  }, [isDragging, mouseOffset, scale])

  // External listeners
  React.useEffect(() => {
    console.log('isDragging', isDragging)
    if (!isDragging) return
    window.addEventListener('mousemove', handleMouseMove)
    window.addEventListener('mouseup', handleMouseUp)
    return () => {
      window.removeEventListener('mousemove', handleMouseMove)
      window.removeEventListener('mouseup', handleMouseUp)
    };
  }, [isDragging, handleMouseMove, handleMouseUp])

  return (
    <text
    ref={textRef}
      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'));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

我还设置position了 mousedown 来消除mouseOffset状态,但没有在这里进行更改,以防发生其他我没有注意到的事情。

无论如何,这将是一个小的优化。


更新

我注意到当片段以全页模式打开时,它不再正确跟踪。

这是由于scale在组件安装上设置的,但如果执行此序列

  • 向上拖动鼠标

  • 调整窗口大小(在代码片段中,选择“整页”)

  • 再次拖动

thenscale对于第二次拖动来说是错误的。

代码现已更新以处理窗口大小调整。