React结合Drag API实现拖拽示例详解

 更新时间:2023年03月06日 11:58:46   作者:samwangdd  
这篇文章主要为大家介绍了React结合Drag API实现拖拽示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

认识拖拽

鼠标拖拽是一个常见的交互场景,在这个熟悉的过程将会发生哪些事件?

拖拽事件指用户通过鼠标(或其他指针设备)将元素移到一个新的位置上。拖拽过程涉及两个对象:被拖拽元素(上图中 A )和可释放目标(上图中 B )

被拖拽元素

默认情况下,图片、链接和文本是可拖动的。HTML5 在所有 HTML 元素上规定了一个 draggable 属性, 表示元素是否可以拖动。图片和链接的 draggable 属性自动被设置为 true,而其他所有元素此属性的默认值为 false。

某个元素被拖动时,会依次触发以下事件:

  • ondragstart:拖动开始,当鼠标按下并且开始移动鼠标时,触发此事件;整个周期只触发一次;
  • ondrag:只要元素仍被拖拽,就会持续触发此事件;
  • ondragend:拖拽结束,当鼠标松开后,会触发此事件;整个周期只触发一次。

可释放目标

当把拖拽元素移动到一个有效的放置目标时,目标对象会触发以下事件:

  • ondragenter:只要一把拖拽元素移动到目标时,就会触发此事件;
  • ondragover:拖拽元素在目标中拖动时,会持续触发此事件;
  • ondragleaveondrop:拖拽元素离开目标时(没有在目标上放下),会触发ondragleave;当拖拽元素在目标放下(松开鼠标),则触发ondrop事件。

🏝 目标元素默认是不能够被拖放的,即不会触发 ondrop 事件,可以通过在目标元素的 ondragover 事件中取消默认事件来解决此问题。

生命周期

拖拽操作中的数据传输

除非数据受影响,否则简单的拖放并没有实际意义。为实现拖动操作中的数据传输,event 对象上暴露了 dataTransfer 对象,用于从被拖动元素向放置目标传递字符串数据。我们使用它来通知画布,当前需要渲染的组件是什么。

dataTransfer 对象主要有两个方法:getData() 和 setData(),分别用来获取和存储值。setData()的第一个参数以及 getData()的唯一参数是一个字符串,表示要设置的数据类型:"text"或"URL"

🏝 虽然这两种数据类型是 IE 最初引入的,但 HTML5 已经将其扩展为允许任何 MIME 类型。为向后 兼容,HTML5 还会继续支持"text"和"URL",但它们会分别被映射到"text/plain"和"text/uri-list”

需要注意的是:存储在 dataTransfer 对象中的数据只能在放置事件中读取。如果没有在 ondrop 事件中取得这些数据,dataTransfer 对象就会被销毁,数据也会丢失。

代码实现

我在项目中使用 React 来实现,并且考虑到跨组件通信,我使用了 dva 来管理数据流。

如何标记当前拖拽的元素?

HTML5 支持的 data-x 属性,我们可以将当前组件的类型 Rectangle 赋值给它,这样处理和画布组件通信方便一些

const Block = (props) => {
  const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
    // 向拖拽数据中添加项目    
    e.dataTransfer.setData('text', e.target.dataset.index);
  };
  return (
    <div onDragStart={handleDragStart}>
      <Button draggable data-index="Rectangle">
        二维码
      </Button>
    </div>
  );
};

在上文中讲到,dataTransfer 的数据必须在 handleDrop 方法中获取。实际的用来保存画布中的所有组件的数据:

function DragEditor(props) {
  const { dvaStore, dispatch } = props;
  // 阻止浏览器默认事件,否则 ondrop 不会触发
  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
  };
  const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    // 获取拖拽元素的组件类型
    const type = e.dataTransfer.getData('text');
    // COMPONENT_LIST 定义了组件的数据格式,根据 type 匹配
    const component = COMPONENT_LIST.filter(
      (i) => i.component === type,
    )[0];
    // 将组件数据添加到 store,画布将会根据数据渲染出组件
    if (component) {
      dispatch?.({
        type: 'store/addComponent',
        payload: component,
      });
    }
  };
  return (...);
}

在画布中拖动

拖动主要依赖组件的初始位置,鼠标开始位置、结束位置。根据后两组得到鼠标移动的距离,和初始位置相加后,得到最终位置。

function DragEditor(props: IEditorProps) {
  const { dvaStore, dispatch } = props;
  const [startAxis, setStartAxis] = React.useState({ x: 0, y: 0 }); // 鼠标开始拖动时的位置
  const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
    setStartAxis({ x: e.clientX, y: e.clientY });
  };
  const handleDragEnd = (e: React.DragEvent<HTMLDivElement>, data: IComponentSchema) => {
    // 鼠标移动的距离
    const displacementX = e.clientX - startAxis.x;
    const displacementY = e.clientY - startAxis.y;
    // 计算组件的终点位置:初始位置 + 鼠标移动的距离
    const endX = Number(data.style.left) + displacementX;
    const endY = Number(data.style.top) + displacementY;
    // 限制坐标的最小值为 0
    const top = Math.max(endY, 0);
    const left = Math.max(endX, 0);
    // 更新当前组件样式
    dispatch?.({
      type: 'store/setShapeStyle',
      payload: { top, left },
    });
  };
  return (
      {dvaStore.componentsData.map((i) => {
        return (
          <RenderComponent
            type={i.component}
            componentData={i}
            key={i.generateId}
            onDragStart={handleDragStart}
            onDragEnd={(e) => handleDragEnd(e, i)}
          />
        );
      })}
  );
}

数据结构

最后,就是组件和数据结构的设计,RenderComponent 是一个自定义的组件,会根据传入的 type 属性渲染对应的组件。组件的数据结构设计如下:

export const COMPONENT_LIST = [
  {
    component: 'Rectangle', // 组件名称
    label: '矩形', // 左侧 Blocks 组件列表中显示的名字
    propValue: '', // 组件所使用的值
    icon: 'BorderOuterOutlined', // 左侧组件列表中显示的 icon 图标
    animations: [], // 动画列表
    events: {}, // 事件列表
    style: {    // 组件样式
      width: 100,
      height: 100,
      top: 0,
      left: 0,
    },
  },
  {
    component: 'Text',
    label: '文字',
    propValue: '文字',
    icon: '',
    animations: [],
    events: {},
    style: {
      width: 200,
      height: 33,
      fontSize: 14,
      fontWeight: 500,
      lineHeight: '',
      letterSpacing: 0,
      textAlign: '',
      color: '',
    },
  },
];

总结

拖拽是非常有趣的一种交互,特别是在低代码场景下非常重要。使用原生 API 能够让我们更加了解底层的一些细节,React 社区也有一些优秀的第三方框架,如:react-dragable, react-beautiful-dnd,大家有兴趣不妨再多了解下。

Links HTML 拖放 API

以上就是React结合Drag API实现拖拽示例详解的详细内容,更多关于React Drag API拖拽的资料请关注脚本之家其它相关文章!

相关文章

  • React实现todolist功能

    React实现todolist功能

    这篇文章主要介绍了React实现todolist功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • React函数组件与类的区别有哪些

    React函数组件与类的区别有哪些

    函数式组件的基本意义就是,组件实际上是一个函数,不是类,下面这篇文章主要给大家介绍了关于React中函数组件与类的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • 使用useImperativeHandle时父组件第一次没拿到子组件的问题

    使用useImperativeHandle时父组件第一次没拿到子组件的问题

    这篇文章主要介绍了使用useImperativeHandle时父组件第一次没拿到子组件的问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Vite搭建React项目的方法步骤

    Vite搭建React项目的方法步骤

    这篇文章主要介绍了Vite搭建React项目的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • React Hooks之使用useCallback和useMemo进行性能优化方式

    React Hooks之使用useCallback和useMemo进行性能优化方式

    这篇文章主要介绍了React Hooks之使用useCallback和useMemo进行性能优化方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • JS跨域解决方案react配置反向代理

    JS跨域解决方案react配置反向代理

    这篇文章主要为大家介绍了JS跨域解决方案react配置反向代理的示例内容详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11
  • 关于react hook useState连续更新对象的问题

    关于react hook useState连续更新对象的问题

    这篇文章主要介绍了关于react hook useState连续更新对象的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • react父组件调用子组件的方式汇总

    react父组件调用子组件的方式汇总

    在react中常用props实现子组件数据到父组件的传递,但是父组件调用子组件的功能却不常用,下面这篇文章主要给大家介绍了关于react父组件调用子组件的相关资料,需要的朋友可以参考下
    2022-08-08
  • 深入理解React与闭包的关系

    深入理解React与闭包的关系

    本文将深入探讨React与闭包之间的关系,我们将首先介绍React和闭包的基本概念,然后详细解释React组件中如何使用闭包来处理状态和作用域的问题,希望通过本文的阅读,你将对React中闭包的概念有更深入的理解,并能够在开发React应用时更好地应用闭包
    2023-07-07
  • 简单的React SSR服务器渲染实现

    简单的React SSR服务器渲染实现

    这篇文章主要介绍了简单的React SSR服务器渲染实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12

最新评论