一百多行代码实现react拖拽hooks

前言

源码总共也就一百多行,看完这个大致可以理解一些成熟的react拖拽库的实现思路,比如react-dnd,然后你上手这些库的时候就非常快了。

使用hooks实现的大致效果动图如下:

我们的目标是实现一个useDrag和useDrop的hooks,类似以下用法就可以轻松让元素可以拖拽,并且在拖拽的各个生命周期,如下,可以自定义传递消息(顺便介绍几个拖拽会触发的事件)。

  • dragstart:用户开始拖拉时,在被拖拉的节点上触发,该事件的target属性是被拖拉的节点。
  • dragenter:拖拉进入当前节点时,在当前节点上触发一次,该事件的target属性是当前节点。通常应该在这个事件的监听函数中,指定是否允许在当前节点放下(drop)拖拉的数据。如果当前节点没有该事件的监听函数,或者监听函数不执行任何操作,就意味着不允许在当前节点放下数据。在视觉上显示拖拉进入当前节点,也是在这个事件的监听函数中设置。
  • dragover:拖拉到当前节点上方时,在当前节点上持续触发(相隔几百毫秒),该事件的target属性是当前节点。该事件与dragenter事件的区别是,dragenter事件在进入该节点时触发,然后只要没有离开这个节点,dragover事件会持续触发。
  • dragleave:拖拉操作离开当前节点范围时,在当前节点上触发,该事件的target属性是当前节点。如果要在视觉上显示拖拉离开操作当前节点,就在这个事件的监听函数中设置。

使用方法 + 源码讲解

class Hello extends React.Component<any, any> {
 constructor(props: any) {
  super(props)
  this.state = {}
 }

 render() {
  return (
   <DragAndDrop>
    <DragElement />
    <DropElement />
   </DragAndDrop>
  )
 }
}

ReactDOM.render(<Hello />, window.document.getElementById("root"))

如上,DragAndDrop组件的作用是给所有的使用useDrag和useDrop的组件传递消息,比如当前拖拽的元素是那个dom,或者你想要其他信息都可以往里面加,我们看看它的实现。

const DragAndDropContext = React.createContext({ DragAndDropManager: {} });
const DragAndDrop = ({ children }) => (
 <DragAndDropContext.Provider value={{ DragAndDropManager: new DragAndDropManager() }}>
  {children}
 </DragAndDropContext.Provider>
)

可以看到传递消息是用react的Context的api去实现的,重点就是这个DragAndDropManager,我们看下实现

export default class DragAndDropManager {

 constructor() {
  this.active = null
  this.subscriptions = []
  this.id = -1
 }

 setActive(activeProps) {
  this.active = activeProps
  this.subscriptions.forEach((subscription) => subscription.callback())
 }

 subscribe(callback) {
  this.id += 1
  this.subscriptions.push({
   callback,
   id: this.id,
  })

  return this.id
 }

 unsubscribe(id) {
  this.subscriptions = this.subscriptions.filter((sub) => sub.id !== id)
 }
}

setActive的作用是用来记录当前drag的元素是哪个,useDrag里面会用到,我们在看useDrag的hooks实现的时候就会明白只要调用setActive方法把drag的dom元素传进去,是不是就知道当前拖拽的元素是哪个了呢。

除此之外,我还增加了订阅事件的api,subscribe,目前我并没有使用它,本次示例里你可以忽略这部分,知道可以添加订阅事件就行。

接着我们看看,useDrag的使用,DragElement的实现如下:

function DragElement() {
 const input = useRef(null)
 const hanleDrag = useDrag({
  ref: input,
  collection: {}, // 这里可以填写任意你想传递给drop元素的消息,后面会通过参数的形式传递给drop元素
 })
 return (
  <div ref={input}>
   <h1 role="button" onClick={hanleDrag}>
    drag元素
   </h1>
  </div>
 )
}

我们就来看下useDrag的实现,非常简单

export default function useDrag(props) {

 const { DragAndDropManager } = useContext(DragAndDropContext)

 const handleDragStart = (e) => {
  DragAndDropManager.setActive(props.collection)
  if (e.dataTransfer !== undefined) {
   e.dataTransfer.effectAllowed = "move"
   e.dataTransfer.dropEffect = "move"
   e.dataTransfer.setData("text/plain", "drag") // firefox fix
  }
  if (props.onDragStart) {
   props.onDragStart(DragAndDropManager.active)
  }
 }

 useEffect(() => {
  if (!props.ref) return () => {}
  const {
   ref: { current },
  } = props
  if (current) {
   current.setAttribute("draggable", true)
   current.addEventListener("dragstart", handleDragStart)
  }
  return () => {
   current.removeEventListener("dragstart", handleDragStart)
  }
 }, [props.ref.current])

 return handleDragStart
}

useDrag做的事情非常简单,

  • 首先通过useContext,来把获取最外层store的数据,也就是上面代码的DragAndDropManager
  • 在useEffect里面,如果外界传入了ref,就将这个dom元素的属性draggable设为true,也就是可拖拽状态
  • 然后给这个元素绑定dragstart事件,注意了,销毁组件的时候我们要移除事件,以防内存泄漏
  • handleDragStart事件首先把外界传的props.collection更新到我们的外界仓库里,这样每一个要drag,也就是拖拽的元素都可以将我们useDrag中传是入的useDrag({collection: {}})信息,通过DragAndDropManager.setActive(props.collection)的方式,传入到外界的store
  • 接着我们dataTransder属性上做一些事,目的是设置元素的拖拽属性为move,并且为了兼容firefox做了处理。
  • 最后每当出发drag事件的时候,外界传入的onDragStart事件也会触发,并且我们将store里的数据传入进去

其中,useDrop的使用,DropElement的实现如下:

function DropElement(props: any): any {
 const input = useRef(null)
 useDrop({
  ref: input,
  // e代表dragOver事件发生时,正在被over的元素的event对象
  // collection是store存储的数据
  // showAfter是表示,是否鼠标拖拽元素时,鼠标经过drop元素的上方(上方就是上半边,下方就是下半边)
  onDragOver: (e, collection, showAfter) => {
  // 如果经过上半边,drop元素的上边框就是红色
   if (!showAfter) {
    input.current.style = "border-bottom: none;border-top: 1px solid red"
   } else {
    // 如果经过下半边,drop元素的上边框就是红色
    input.current.style = "border-top: none;border-bottom: 1px solid red"
   }
  },
  // 如果在drop元素上放开鼠标,则样式清空
  onDrop: () => {
   input.current.style = ""
  },
  // 如果在离开drop元素,则样式清空
  onDragLeave: () => {
   input.current.style = ""
  },
 })
 return (
  <div>
   <h1 ref={input}>drop元素</h1>
  </div>
 )
}

最后,我们来看看useDrop的实现

export default function useDrop(props) {
// 获取最外层store里的数据
 const { DragAndDropManager } = useContext(DragAndDropContext)
 const handleDragOver = (e) => {
 // e就是拖拽的event对象
  e.preventDefault()
  // getBoundingClientRect的图请看下面
  const overElementHeight = e.currentTarget.getBoundingClientRect().height / 2
  const overElementTopOffset = e.currentTarget.getBoundingClientRect().top
  // clientY就是鼠标到浏览器页面可视区域的最顶端的距离
  const mousePositionY = e.clientY
  // mousePositionY - overElementTopOffset就是鼠标在元素内部到元素border-top的距离
  const showAfter = mousePositionY - overElementTopOffset > overElementHeight
  if (props.onDragOver) {
   props.onDragOver(e, DragAndDropManager.active, showAfter)
  }
 }
 // drop事件
 const handledDop = (e: React.DragEvent) => {
  e.preventDefault()

  if (props.onDrop) {
   props.onDrop(DragAndDropManager.active)
  }
 }
 // dragLeave事件
 const handledragLeave = (e: React.DragEvent) => {
  e.preventDefault()

  if (props.onDragLeave) {
   props.onDragLeave(DragAndDropManager.active)
  }
 }
  // 注册事件,注意销毁组件时要注销事件,避免内存泄露
 useEffect(() => {
  if (!props.ref) return () => {}
  const {
   ref: { current },
  } = props
  if (current) {
   current.addEventListener("dragover", handleDragOver)
   current.addEventListener("drop", handledDop)
   current.addEventListener("dragleave", handledragLeave)
  }
  return () => {
   current.removeEventListener("dragover", handleDragOver)
   current.removeEventListener("drop", handledDop)
   current.removeEventListener("dragleave", handledragLeave)
  }
 }, [props.ref.current])
}

getBoundingClientRect的api图解:

rectObject = object.getBoundingClientRect();

rectObject.top:元素上边到视窗上边的距离;

rectObject.right:元素右边到视窗左边的距离;

rectObject.bottom:元素下边到视窗上边的距离;

rectObject.left:元素左边到视窗左边的距离;

到此这篇关于一百多行代码实现react拖拽hooks的文章就介绍到这了,更多相关react拖拽hooks内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • react.js组件实现拖拽复制和可排序的示例代码

    在实现复制前,对之前的拖拽排序组件属性进行了修改. 摒弃了value中的content属性,拖拽组件暴露的render函数,利用这个属性进行组件内部子组件的渲染,这点主要是参考了蚂蚁金服的Ant design里面一些组件的设计. 为了实现Data和model的脱藕,和sortKey一样,组件增加codeKey属性. 拖拽复制的效果如下: 由于实现组件的核心是根据value数据来渲染页面,因此实现拖拽复制功能,只需要在"拖拽释放"的时候,将被拖拽方的数据放到当前目标所在的value数组中

  • 再次谈论React.js实现原生js拖拽效果引起的一系列问题

    React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站.做出来以后,发现这套东西很好用,就在2013年5月开源了.由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单.所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具. 前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘

  • React 实现拖拽功能的示例代码

    本文介绍了React 实现拖拽功能的示例代码,分享给大家,具体如下: 实现效果: 因为工作中会用到 JIRA 所以想实现一下相似的功能,顺便学习一下 H5 的拖拽.不支持拖拽改变顺序,感觉有点麻烦,而且没必要.感觉相关的博文好少的,大部分都是直接上代码,没有解释. 图片默认可以拖动,其他元素的拖动效果同图片.正常的 div 是不能被拖动的,鼠标点击选择后移动没有效果,需要加  draggable="true" 使得元素可以被拖动. 拖拽相关的几个事件,有被拖动元素的事件,也有拖动进入的

  • 基于React.js实现原生js拖拽效果引发的思考

    一.起因&思路 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖拽效果. 首先,其实拖拽效果的思路是很简单的.主要就是三个步骤: 1.onmousedown的时候,启动可拖拽事件,记录被拖拽元素的原始坐标参数. 2.onmousemove的时候,实时记录鼠标移动的距离,结合被拖拽元素第一阶段的坐标参数,计算并设置新的坐标值. 3.onmouseup的时候,关闭可拖拽事件,记录新的坐标值. 注意:这里主要是通过绝对定位的top和left来确定元素的位置

  • React.js组件实现拖拽排序组件功能过程解析

    因为使用了react.js技术栈,所以封装优先考虑输入和输出.基于数据驱动去渲染页面.控制拖拽元素的顺序. 由于我不考虑兼容IE8等旧版本浏览器,拖拽的效果采用了HTML5的拖放(Drag 和 drop).当然,如果要求兼容性丰富,使用鼠标点击的相关事件也很简单. 实现的效果如下: 第一步是先了解H5拖放的相关属性,MDN上有详细的说明,链接 有一点需要注意的是,react.js会给所有的属性事件名称前加上"on",后面则为驼峰式写法.例如原生的click事件,在react.js里应使

  • 一百多行代码实现react拖拽hooks

    前言 源码总共也就一百多行,看完这个大致可以理解一些成熟的react拖拽库的实现思路,比如react-dnd,然后你上手这些库的时候就非常快了. 使用hooks实现的大致效果动图如下: 我们的目标是实现一个useDrag和useDrop的hooks,类似以下用法就可以轻松让元素可以拖拽,并且在拖拽的各个生命周期,如下,可以自定义传递消息(顺便介绍几个拖拽会触发的事件). dragstart:用户开始拖拉时,在被拖拉的节点上触发,该事件的target属性是被拖拉的节点. dragenter:拖拉进

  • React拖拽调整大小的组件

    本文实例为大家分享了React拖拽调整大小的组件,供大家参考,具体内容如下 一.实现流程 1.使用React.cloneElement加强包裹组件,在包裹的组件设置绝对定位,并在组件内加上四个可调整大小的拖动条,在点击拖动条并进行拖动时会改变DragBox的大小,如下: 2.使用: <DragBox dragAble={true} minWidth={350} minHeight={184} edgeDistance={[10, 10, 10, 10]} dragCallback={this.d

  • react拖拽组件react-sortable-hoc的使用

    目录 1.文件1 2.文件2 3.使用 使用react-sortable-hoc实现拖拽 如图: 提示:下面案例可供参考 1.文件1 代码如下(示例):文件名称:./dragcomponents import * as React from 'react' import { sortableContainer, sortableElement, sortableHandle, } from "react-sortable-hoc"; // 拖拽的关键组件 const Sortable:

  • 30行代码实现React双向绑定hook的示例代码

    目录 使用Proxy代理数据 使用useRef创建同一份数据引用 添加更新handler 去除多次Proxy 添加缓存完善代码 总结 Sandbox 示例 Vue和MobX中的数据可响应给我们留下了深刻的印象,在React函数组件中我们也可以依赖hooks来实现一个简易好用的useReactive. 看一下我们的目标 const CountDemo = () => { const reactive = useReactive({ count: 0, }); return ( <div onCl

  • javascript支持firefox,ie7页面布局拖拽效果代码

    javascript 拖拽JavaScript Google IG Drag Demo,非常棒的拖动,准备用于F2Blog新Theme的后台模块设置,之间的拖 动 拖拽效果的页面效果演示地址:http://img.jb51.net/online/tuozhuai/google_drag.htm加强版效果演示地址:http://img.jb51.net/online/tuozhuai/google_drag2.htm拖拽原理: 关于拖拽的基础,可以参考这篇文章,讲得非常不错. 其实原理很简单,就是

  • React DnD如何处理拖拽详解

    目录 正文 代码结构 DndProvider DragDropManager useDrag HTML5Backend TouchBackend 总结 正文 React DnD 是一个专注于数据变更的 React 拖拽库,通俗的将,你拖拽改变的不是页面视图,而是数据.React DnD 不提供炫酷的拖动体验,而是通过帮助我们管理拖拽中的数据变化,再由我们根据这些数据进行渲染.我们可能需要写额外的视图层来完成想要的效果,但是这种拖拽管理方式非常的通用,可以在任何场景下使用.初次使用可能感觉并不是那

  • React实现卡片拖拽效果流程详解

    前提摘要: 学习宋一玮 React 新版本 + 函数组件 &Hooks 优先 开篇就是函数组件+Hooks 实现的效果如下: 学到第11篇了 照葫芦画瓢,不过老师在讲解的过程中没有考虑拖拽目标项边界问题,我稍微处理了下这样就实现拖拽流畅了 下面就是主要的代码了,实现拖拽(src/App.js): 核心在于标记当前项,来源项,目标项,并且在拖拽完成时对数据处理,更新每一组数据(useState): /** @jsxImportSource @emotion/react */ // 上面代码是使用e

  • JS拖拽的进一步练习,移动与拉伸实现代码

    直接在本地是支持IE6的,有个问题就是,鼠标点击的时候光标会变为选择文字的光标,不知道应该怎么处理这个问题呢? 在此记录一下代码: 简单拖拽扩展 *{margin:0;padding:0;} #outwarp{ margin:20px auto; width:600px; height:600px; background:#fff; border:1px solid #333; position: relative; } .controlBox{ width:200px; height:200p

  • antdesign-vue结合sortablejs实现两个table相互拖拽排序功能

    实现效果 本来想在网上看看有没有基于antdesign做的,然后发现是真的少啊!废话不多说,先上图: sortablejs介绍 首先先来认识一下这个插件: sortablejs 大家可以去细读一下它的api文档: 这边我就着重介绍一下我用到的api. 1.group可以传入对象,参数值为name,pull,put, name:如果是要两个列表下进行拖动的话,name的值必须为一样: pull:pull用来定义从这个列表容器移动出去的设置,true/false/'clone'/function t

  • 拖拽插件sortable.js实现el-table表格拖拽效果

    目录 问题描述 案例一 简单拖拽 代码附上 案例二 el-table表格拖拽 效果图 代码附上 问题描述 Sortable.js是一款优秀的js拖拽库,因为是原生js写的,所以性能不错,也支持移动端哦.本文举两个案例来学习一下. 案例一 简单拖拽 效果图 代码附上 关于理解看注释哦,运行的话复制粘贴即可 <!DOCTYPE html> <html> <head>     <meta charset="utf-8" />     <t

随机推荐