【问题标题】:How to create a resizable component in React如何在 React 中创建可调整大小的组件
【发布时间】:2020-03-21 19:23:44
【问题描述】:

正如标题中所说。我想创建一个 React 组件,它可以让我通过拖动来调整其宽度 - 就像 Windows 操作系统中的窗口一样。实际上处理这个问题的最佳方法是什么?

编辑
我包括了我目前对此事的处理方法:

首先,我在容器的右上角放置了一个“拖动器”元素。当我在该元素上按下鼠标时,我想创建一个 mousemove 事件侦听器,它将修改相对于光标的 X 坐标相对于容器边缘的初始 X 位置的 containerWidth。我已经让那个事件监听器在按住鼠标按钮后触发并记录我的坐标,但不幸的是,由于某种原因,在鼠标未按下(mouseUp 事件)后事件没有被删除,这不是我想要的。任何建议表示赞赏,还有关于我将来可能期望与该主题相关的一些问题的建议。谢谢。

type Props = MasterProps & LinkStateToProps & LinkDispatchToProps;
const Test3 = (Props: Props) => {

  const [containerWidth, setContainerWidth] = React.useState(640)
  const [isBeingStretched, setIsBeingStretched] = React.useState(false);
  const masterRef = React.useRef(null);


  const logMousePosition = React.useCallback((event:MouseEvent)=>{
    console.log(event.clientX);
  },[])


  const handleMouseDown=()=>{
    document.addEventListener('mousemove', logMousePosition);
    masterRef.current.addEventListener('mouseup', ()=>{
      document.removeEventListener('mouseup', logMousePosition)
    })
  }
  const handleMouseUp = () => { 
    document.removeEventListener('mousemove', logMousePosition);
  }
  return (
    <div className="master-wrapper" ref={masterRef}>
      <div className="stretchable-div" style={{ width: `${containerWidth}px` }}>
        <div className="dragger-wrapper">
          <h2>This is supposed to change width</h2>
          <div className="dragger"
            id="dragger"
            onMouseDown={handleMouseDown}
            onMouseUp={handleMouseUp}/>
        </div>
      </div>

    </div>
  );

}

export default connect(mapStateToProps, mapDispatchToProps)(Test3);

【问题讨论】:

  • 到目前为止你尝试了什么?
  • 一般的做法可能是这样的:在mouseDown 上获取用户的x 和y 屏幕坐标 > 获取mouseMove 每一帧上的坐标之间的差值 > 将这些值转换为一些新的@相关元素的 987654324@ 或 transform 样式。
  • @JMadelaine 我进行了编辑并包含了我当前的方法
  • @LawrenceWitt 这正是我之前的计划。我进行了编辑并包含了一些代码,我非常感谢一些建议

标签: reactjs


【解决方案1】:

我以前从来没有做过这样的事情,所以我决定试一试,结果用 React 状态管理实现它非常简单。如果您是 React 新手,我可以理解为什么您可能不知道从哪里开始,这没关系,尽管在我解决我的解决方案之前需要注意两点:

  1. document.getElementByIddocument.addEventListener 等语句将不再按预期运行。使用 React,您正在操作一个虚拟 DOM,它会为您更新实际的 DOM,您应该尽可能让它这样做。
  2. 使用refs 来解决这个问题是不好的做法。它们的行为方式可能与上述陈述类似,但这不是它们的预期用例。阅读documentationref 良好用例的评价。

这是我演示的 JSX 部分的样子:

return (
    <div className="container" onMouseMove={resizeFrame} onMouseUp={stopResize}>
      <div className="box" style={boxStyle}>
        <button className="dragger" onMouseDown={startResize}>
          Size Me
        </button>
      </div>
    </div>
  );

我们将需要三个不同的事件 - onMouseDownonMouseMoveonMouseUp - 来跟踪调整大小的不同阶段。您已经在自己的代码中做到了这一点。在 React 中,我们将所有这些声明为元素本身的属性,尽管它们实际上并不是内联函数。 React 在后台为我们添加它们作为事件监听器。

const [drag, setDrag] = useState({
    active: false,
    x: "",
    y: ""
});

const startResize = e => {
    setDrag({
      active: true,
      x: e.clientX,
      y: e.clientY
    });
};

我们将使用一些状态来跟踪正在进行的调整大小。我将所有内容压缩为一个对象以避免膨胀并使其更具可读性,尽管如果您有其他挂钩(例如 useEffectuseMemo 依赖于该状态,则这并不总是理想的。第一个事件只是保存用户鼠标的初始 x 和 y 位置,并将 active 设置为 true 以供下一个要引用的事件。

const [dims, setDims] = useState({
    w: 200,
    h: 200
});

const resizeFrame = e => {
    const { active, x, y } = drag;
    if (active) {
      const xDiff = Math.abs(x - e.clientX);
      const yDiff = Math.abs(y - e.clientY);
      const newW = x > e.clientX ? dims.w - xDiff : dims.w + xDiff;
      const newH = y > e.clientY ? dims.h + yDiff : dims.h - yDiff;

      setDrag({ ...drag, x: e.clientX, y: e.clientY });
      setDims({ w: newW, h: newH });
    }
};

第二个状态将初始化,然后随着元素值的变化更新元素的尺寸。这可以使用您想要的任何度量,尽管它必须与某些 CSS 属性相关。

resizeFrame 函数执行以下操作:

  1. 通过解构赋值使drag 的属性易于使用。这将使代码更具可读性和更易于键入。
  2. 检查调整大小是否处于活动状态。 onMouseMove 将针对鼠标在相关元素上移动的每个像素触发,因此我们要确保它处于适当的条件下。
  3. 使用Math.abs() 将当前鼠标位置与保存的鼠标位置之间的值差作为正整数。这将使我们不必执行第二轮条件语句。
  4. 根据新的鼠标位置是大于还是小于任一轴上的先前位置,使用 turnary 语句添加或减去尺寸差异。
  5. 用新值设置状态,使用扩展运算符... 保留drag 的无关部分。
const stopResize = e => {
    setDrag({ ...drag, active: false });
};

const boxStyle = {
    width: `${dims.x}px`,
    height: `${dims.y}px`
};

然后,一旦用户完成,我们只需将拖动状态的活动设置为 false。 JS 样式对象被传递给带有状态变量的元素,以便自动处理。

这是完成效果的codesandbox


这样做的一个缺点是它基本上要求您将mouseMove 事件侦听器分配给最大的站点容器,因为在调整大小期间鼠标不会停留在框的范围内.如果您想拥有多个具有相同功能的元素,这可能是一个问题,尽管没有什么是您无法通过良好的状态管理解决的。您可能可以对此进行微调,以使鼠标始终停留在拖动元素上,尽管这需要更复杂的实现。

【讨论】:

  • 这是一个很好的响应,代码运行非常流畅!最后一个问题是,最终将在视口中调整大小的不止 1 个容器(但显然不是同时),并且这些容器的宽度将以牺牲彼此为代价来调整大小。由于容器的宽度将保持在内部状态,这应该不会造成问题,但期望的结果是容器的每个边缘都可以独立调整大小,以便一次只影响一个邻居。您将如何处理这个问题,这会对代码产生重大影响吗?
  • 基本工作流程将保持不变(即创建状态 > 比较状态 > 更新状态),但这确实使事情变得更加复杂。宽度将不再容易改变。我的第一个想法是更新transform: translateX(),将transform-origin 设置为leftright,具体取决于您拖动的边缘。您还必须考虑resizeFrame 中的其他容器。稍后我可能会尝试一下,因为这个问题引起了我的兴趣,尽管我建议您尝试一下,看看你能把什么放在一起!
  • 我想我找到了一个更简单的解决方案。只需要放置一个空容器 div,紧挨着正在扩展的容器,只有对面的边缘会伸展。我认为基本上放置一个空容器更容易,而且一切似乎都很好!
  • 很高兴您有工作要做 - 这听起来确实更易于管理!这就是我正在努力争取的价值:user sizeable grid。非常错误和过度劳累,并且无法正常工作,我认为它会回到绘图板上 - 我实际上在我正在计划的项目中需要这样的东西,所以我最终必须弄清楚...
猜你喜欢
  • 1970-01-01
  • 2010-09-13
  • 1970-01-01
  • 1970-01-01
  • 2016-11-28
  • 2019-10-10
  • 2015-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多