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

Next.js使用react-moveable/react-selecto时onDragGroup失效如何解决

问题背景
  • 技术栈:React v18.1.0、Next.js v12.1.6,使用react-moveable、react-selecto实现画布元素拖拽多选能力
  • 异常表现:单选元素拖拽时onDrag事件正常触发,多选元素后触发组拖拽时,onDragGroup系列事件完全不响应
  • 初始怀疑方向:是否由Next.js的SSR渲染机制导致兼容性问题

附原业务实现代码:

import React, { useRef, useState } from "react";
import Moveable from "react-moveable";
import Selecto from "react-selecto";

export default function MoveableProvider() {

  const [targets, setTargets] = useState([]);
  const [frameMap] = useState(() => new Map());
  const selectoRef = useRef(null);
  const moveableRef = useRef(null);

  return (
    <div className="container">
      <Moveable
        ref={moveableRef}
        draggable={true}
        target={targets}
        onClickGroup={(e) => {
          selectoRef.current.clickTarget(e.inputEvent, e.inputTarget);
        }}
        onDrag={(e) => {
          const target = e.target;
          const frame = frameMap.get(target);

          frame.translate = e.beforeTranslate;
          target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`;
        }}
        onDragStart={(e) => {
          const target = e.target;

          if (!frameMap.has(target)) {
            frameMap.set(target, {
              translate: [0, 0],
            });
          }
          const frame = frameMap.get(target);

          e.set(frame.translate);
        }}
        onDragGroupStart={(e) => {
          e.events.forEach((ev) => {
            const target = ev.target;

            if (!frameMap.has(target)) {
              frameMap.set(target, {
                translate: [0, 0],
              });
            }
            const frame = frameMap.get(target);

            ev.set(frame.translate);
          });
        }}
        onDragGroup={(e) => {
          e.events.forEach((ev) => {
            const target = ev.target;
            const frame = frameMap.get(target);

            frame.translate = ev.beforeTranslate;
            target.style.transform = `translate(${frame.translate[0]}px, ${frame.translate[1]}px)`;
          });
        }}
      />
      <Selecto
        ref={selectoRef}
        dragContainer={".elements"}
        selectableTargets={[".selecto-area .cube"]}
        hitRate={0}
        selectByClick={true}
        selectFromInside={false}
        toggleContinueSelect={["shift"]}
        ratio={0}
        onDragStart={(e) => {
          const moveable = moveableRef.current;
          const target = e.inputEvent.target;
          k;
          if (
            moveable.isMoveableElement(target) ||
            targets.some((t) => t === target || t.contains(target))
          ) {
            e.stop();
          }
        }}
        onSelectEnd={(e) => {
          const moveable = moveableRef.current;
          setTargets(e.selected);

          if (e.isDragStart) {
            e.inputEvent.preventDefault();

            setTimeout(() => {
              moveable.dragStart(e.inputEvent);
            });
          }
        }}
      ></Selecto>

      <div className="elements selecto-area" style={{ display: "flex" }}>
        <div
          className="cube target"
          style={{
            width: 250,
            height: 200,
            background: "green",
          }}
        ></div>

        <div
          className="cube target"
          style={{
            width: 250,
            height: 200,
            background: "yellow",
          }}
        ></div>

        <div
          className="cube target"
          style={{
            width: 250,
            height: 200,
            background: "aqua",
          }}
        ></div>

        <div
          className="cube target"
          style={{
            width: 250,
            height: 200,
            background: "red",
          }}
        ></div>
      </div>
      <div className="empty elements"></div>
    </div>
  );
}
问题根因

该异常由三个问题共同导致,SSR机制确实是诱因之一:

  1. SSR渲染兼容性问题:react-moveable和react-selecto强依赖浏览器端DOM API与全局window对象,Next.js默认服务端预渲染时会生成不匹配的组件实例,导致多目标场景下的组事件监听绑定失效
  2. 代码语法错误:Selecto组件的onDragStart回调中存在无意义的孤立语句k;,多选拖拽触发时会直接抛出ReferenceError: k is not defined,中断后续事件流,导致组拖拽逻辑完全无法执行
  3. 组件配置缺失:Moveable组件未开启组拖拽相关的必要属性,传入多目标数组时不会自动切换到组拖拽模式,仍然走单元素拖拽逻辑
修复方案

按以下步骤调整即可解决问题:

  1. 禁用第三方组件SSR加载:使用Next.js提供的动态导入能力,将两个依赖包改为仅客户端渲染,替换原有静态导入语句:
import dynamic from "next/dynamic";
// 替换原有的import Moveable/Selecto语句
const Moveable = dynamic(() => import("react-moveable"), { ssr: false });
const Selecto = dynamic(() => import("react-selecto"), { ssr: false });
  1. 清除语法错误:删除Selecto的onDragStart回调中孤立的k;语句,避免运行时报错中断事件
  2. 补充Moveable组拖拽配置:在Moveable组件上新增两个属性,开启多目标组拖拽能力:
<Moveable
  ref={moveableRef}
  draggable={true}
  groupable={true} // 新增:开启组拖拽模式
  individualGroupable={true} // 新增:支持单/多目标拖拽模式自动切换
  target={targets}
  // 其余原有回调逻辑保持不变
/>
  1. (可选)适配React 18严格模式:如果项目开启了React 18严格模式,将Moveable、Selecto的ref实例校验逻辑放到useEffect中执行,避免严格模式双渲染导致的ref引用错乱

修复完成后,多选元素拖拽时onDragGroupStart、onDragGroup事件均可正常触发,单元素拖拽逻辑不受影响。

内容的提问来源于stack exchange,提问作者sorena-paydar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 10:06:19