You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Final Form拖拽元素后缩放定位异常问题排查

React Final Form + dndkit + react-zoom-pan-pinch 拖拽后缩放定位错误问题

问题场景

用React Final Form构建带可拖拽盒子的表单,拖拽功能基于dndkit,元素缩放用react-zoom-pan-pinch。给元素添加了点击缩放逻辑:

onClick={() => {
  zoomToElement(question.id, 1.5);
}}

未拖拽时缩放功能正常,但拖拽完成后点击缩放,会定位到元素的初始位置而非当前拖拽后的位置,推测是缩放时仍在使用表单的初始状态。

表单核心代码如下:

<Form
  onSubmit={submit}
  initialValues={initializeQuestions}
  mutators={{ ...arrayMutators }}
  validate={validateQuestions}
>
  {({ form, handleSubmit, values, pristine }) => {
    return (
      <form onSubmit={handleSubmit}>
        <DndContext
          onDragStart={(e) => {
            setDragPoint(e.active.id.toString());
          }}
          onDragEnd={(e) => {
            setDragPoint(null);
            handleDragEnd(e);
          }}
        >
          <TransformWrapper>
            {({ 
              zoomIn,
              zoomOut,
              resetTransform,
              zoomToElement,
              centerView,
              setTransform,
              state,
              ...rest
            }) => {
              zoomPanPinchState = { ...state };
              zoomToElement = zoomToElement;
              setTransform = setTransform;
              return (
                <>
                  <TransformComponent
                    wrapperStyle={{
                      width: '100%',
                      height: '100%',
                      position: 'absolute',
                    }}
                    contentStyle={{ width: '100%', height: '100%' }}
                  >
                    <FieldArray name="questions">
                      {({ fields }) => (
                        <>
                          {fields.map((name, index) => (
                            <div>
                              <BoxWrapper
                                id={fields.value[index].id}
                                name={`questions[${index}]`}
                              />
                            </div>
                          ))}
                        </>
                      )}
                    </FieldArray>
                  </TransformComponent>
              </TransformWrapper>
            </DndContext>
          </form>
        );
      }}
    </Form>

解决思路及步骤

1. 确认拖拽后位置已同步到表单值

检查handleDragEnd函数,确保拖拽结束后,元素的新坐标(如x、y值)已经更新到Final Form的questions数组对应项中。示例代码:

const handleDragEnd = (event) => {
  const { active, over } = event;
  if (over) {
    // 假设已获取到拖拽后的新坐标newX、newY
    const targetIndex = fields.value.findIndex(item => item.id === active.id);
    form.change(
      `questions[${targetIndex}].position`, 
      { x: newX, y: newY }
    );
  }
};

2. 让BoxWrapper根据表单值渲染真实位置

BoxWrapper必须从Final Form的字段中读取最新的位置值,设置对应的样式,保证DOM元素的位置和表单数据同步。示例:

const BoxWrapper = ({ name, id }) => {
  const { input } = useField(name);
  const { position } = input.value;

  return (
    <div
      id={id}
      style={{
        position: 'absolute',
        left: position.x + 'px',
        top: position.y + 'px',
        // 其他自定义样式
      }}
      onClick={() => {
        zoomToElement(id, 1.5);
      }}
    >
      {/* 盒子内部内容 */}
    </div>
  );
};

3. 替换全局变量,用React钩子存储缩放引用

当前用全局变量存储zoomPanPinchState、zoomToElement等,容易导致状态不同步,建议换成useRef和useState保存:

const zoomToElementRef = useRef(null);
const setTransformRef = useRef(null);
const [zoomState, setZoomState] = useState({});

// 在TransformWrapper的回调里更新引用
{({ zoomToElement, setTransform, state, ...rest }) => {
  zoomToElementRef.current = zoomToElement;
  setTransformRef.current = setTransform;
  setZoomState(state);
  // 后续渲染逻辑
}}

之后在BoxWrapper的点击事件里,用zoomToElementRef.current(id, 1.5)调用缩放方法。

4. 拖拽后强制同步缩放组件状态(可选)

如果前面几步完成后仍有问题,在拖拽结束后手动触发缩放组件更新,让它重新读取元素的DOM位置:

const handleDragEnd = (event) => {
  // ... 先执行更新表单值的逻辑
  // 强制同步缩放状态
  if (setTransformRef.current && zoomState) {
    setTransformRef.current(zoomState.scale, zoomState.positionX, zoomState.positionY);
  }
};

内容的提问来源于stack exchange,提问作者Shan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 23:31:04