vue3自定义拖拽,vue3-dnd 的使用

这篇具有很好参考价值的文章主要介绍了vue3自定义拖拽,vue3-dnd 的使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

资料地址:

入门文档:https://hcg1023.github.io/vue3-dnd/guide/
案例文档:https://hcg1023.github.io/vue3-dnd/example/
react-dnd:https://react-dnd.github.io/react-dnd/docs/overview

踩坑记录:

1、安装时需要安装react-dnd-html5-backend npm install vue3-dnd react-dnd-html5-backend

2、标签要包裹在app外层,而不是放在你想要拖拽的外侧,否则切换路由会报错

 <DndProvider :backend="HTML5Backend"></DndProvider> 
// 标签要包裹在app外层,而不是放在你想要拖拽的外侧,否则切换路由会报错

3、使用useDrag找到对应标签时 drag对应标签中的 :ref=“drag” 因为是通过ref获取到标签,所以标签不能是v-for的,如果是v-for的子集标签则需要单独拎出来一个组件,组件内使用useDrag()

const [, drag] = useDrag(() => ({})
// 使用useDrag找到对应标签时 drag对应标签中的  :ref="drag" 因为是通过ref获取到标签,所以标签不能是v-for的,如果是v-for的子集标签则需要单独拎出来一个组件,组件内使用useDrag()

4、拖拽预览的自定义(我认为一个巨坑的地方)

(1)、通过导入一个getEmptyImage的空图片将原有的拖拽中的样式隐藏
const [, drag, preview] = useDrag(() => ( // 定义对应的preview

onMounted(() => {
    preview(getEmptyImage(), { captureDraggingState: true })
})
(2)、通过useDragLayer定义一个自定义的拖拽层、并且这个拖拽层最好是定义在循环组件外层,只创建一个即可,拖拽时会自动找到这个拖拽层
const collect = useDragLayer((monitor) => {})
 (3)、然而这样还远没有结束,因为虽然定义好了拖拽层但是拖拽时拖拽层是不会跟着你移动的,它相当于一个固定的DOM在页面上而已   
	:style="getItemStyles(initialOffset, currentOffset, dragParams)" // 需要通过修改它的style,利用组件提供的,
	
	// 其中的参数initialOffset/currentOffset是从useDragLayer收集器导出的 
 	const collect = useDragLayer((monitor) => {
  		return {
    		dragParams: monitor.getItem(), // 拖拽信息
    		initialOffset: monitor.getInitialSourceClientOffset(), // 拖动开始时,拖动源的根dom对于客户端初始位置 固定不变)
    		currentOffset: monitor.getSourceClientOffset(), // 当前拖动源的根dom节点相对于客户端偏移量
    		initialClientOffset: monitor.getInitialClientOffset(), // 开始拖动时,鼠标位置(固定不变)
  		}
	})
	const { initialOffset, currentOffset, dragParams, initialClientOffset } = toRefs(collect)

	// 通过当前拖动的根节点偏移量,改变拖拽预览style的translate,让它跟上鼠标
	let { x, y } = currentOffset  
	const transform = `translate(${x}px, ${y}px)`
	return {transform} 

另外:还存在一个小坑,如果被拖动的元素比较大,你从元素的某个角拖动起来时拖动,预览的位置会离鼠标指针比较远,考虑到问题并不大,有的同学没有遇到该问题,此处不做过多的赘述了。如果有遇到这个问题,我也在下面分享了我的解决方案。

5、引入toRefs不是通过vue,而是@vueuse/core

import { toRefs } from '@vueuse/core'

vue3自定义拖拽,vue3-dnd 的使用
注:如果你只是需要看一看有什么坑,本人的踩坑记录到这里基本已经完事了。基本功能已经可以照着文档实现一下了。
注:因为接下来的文档中会带有比较多的业务逻辑,看不懂的同学无需气馁,是因为我写的不够好而不是你的问题。(而且很多实现方式我也并不知道是不是最优解)
注:如果你实在感兴趣,那鄙人只能继续展示拙见了。

其他功能:可放置区域颜色高亮(位置限制)、可放置位置展示竖线,拖动预览始终在拖动光标中心

vue3自定义拖拽,vue3-dnd 的使用

1、让可放置区域亮起来(如果可放置区域不一定?):
通过:style样式绑定backgroundColor
// canDrop 为收集器导出的可以放置标识
// canDropShow 为自定义的可放置区域
const backgroundColor = computed(() => {
  return unref(canDrop) && unref(canDropShow) ? '#F8F8FA' : ''
})
// 可放置位置限制,首先限制canDrop(拖动中,并且拖动开始区域拖动类型存在放置区域中),后面的dragItem逻辑可以不断叠加
const canDropShow = computed(() => {
	if(unref(canDrop) && dragItem.value.from === ''){
		return true
	}
	return false
})

// 将canDropShow放到useDrop的drop中,来限制可以放置
const [dropCollect, chipDrop] = useDrop(() => ({
 	drop: (obj: any, monitor: any) => {
	 	if (unref(canDropShow)) {
			**放置后的逻辑**
		}
		return // return false即为不可放置
	}
})

// 注意:1)拖拽收集器中提供了canDrop方法,并且此方法支持传入一个函数进行重写,但是重写函数中是不可以使用canDrop,因此没有选择重写此方法,而是重新放了一个computed属性(当然你可以选择试一试可否重写)
	(2)还有一种想象中的实现方式,我也并不知道会不会优于我这种:因为canDrop代表着isDragging&&拖动类型对应,那是否可以给useDrag的type设置独立的拖拽类型,useDrop的accept接收的也是动态的类型,从而让类型对应的可放置。这样我们在放置限制时,只需要使用canDrop即可。可以试一下,欢迎分享
2、使用线来标识可放置的位置(我们期望的是放置后才进行数据结构的改变,再进行视图更新。官网中有一个放在此位置就把下一个标签挤开的案例,是通过hover的时候就改变了数据来实现的,显然不是我们想要的):
// 线的展示隐藏我是通过自定义标签中的伪类进行实现的 (当然也可以通过放置一个div元素,然后移动它的位置来展示相应的线)
// 这里还遇到一个小坑:刚开始我是使用的width来展示线的宽度,但是发现线的宽度在屏幕上不同位置表现不一致,移动leftLine的left会发现线会变粗变细,所以就改用border-left了
.leftLine::before {
  position: absolute;
  top: 0;
  left: -5px;
  height: 100%;
  content: '';
  border-left: 2px solid #824dfc;
}

.rightLine::after {
  position: absolute;
  top: 0;
  right: -8px;
  height: 100%;
  content: '';
  border-left: 2px solid #824dfc;
}

vue3自定义拖拽,vue3-dnd 的使用
如果是同一行标签过多,会出现换行的情况,处理标签位置时就需要先计算每一行的高度,判断其放在那一行,再计算每一行的第几个位置:

注意:
(1)我刚开始判断可放置线是将每个标签item中 设置了可放置,这样在拖拽到标签上时就可以通过提供的hover回调判断可放置区域,但是这样有一个明显缺陷是在每一行的空白区域其实也是应该可以放置的,因此这个方案成功被放弃。
(2)线的展示应该按照我下面图片的位置进行划分,我们要将放置到空白区域划分到某个标签范围内。
(3)还有一点是,标签的位置是通过原生js获取的,而鼠标位置可以通过dnd插件提供的回调进行获取。原生的getBoundingClientRect是有性能消耗的,虽然不大,但是确实有。
(4)我是将数据处理成了二维数组,每一行内折行都是一个子数组,从而每个子数组的高度都是一样的,把当前鼠标高度确定到每个子数组对应的高度,再在此子数组中找到对应横坐标位置,从而确定线应该出现在哪个位置

vue3自定义拖拽,vue3-dnd 的使用

// hover的时候判断线的展示位置
const [dropCollect, chipDrop] = useDrop(() => ({
  accept: ['可放置区域对应useDrag的type'],
  drop: (obj: any, monitor: any) => {
   // 处理放置后的数据
  },
  collect(monitor) {
    return {
      dragItem: monitor.getItem(),
      handlerId: monitor.getHandlerId(),
      canDrop: monitor.canDrop(),
      isOver: monitor.isOver(),
      didDrop: monitor.didDrop(),
      // isOverCurrent: monitor.isOver({ shallow: true }),
    }
  },
  hover: (_item: any, monitor: any) => {
	lineShow(monitor) // hover时调用线的展示方法
  },
}))

/**
 * @chipsList 获取当前行中的标签dom
 * @domList 获取当前行dom
 * @curRowPosition 获取当前行的位置
 * @labelPosition 标签位置
 * @mousePosition 鼠标位置
 * @midLine 横向中线
 */
const lineShow = (monitor) => {
  if (unref(canDropShow)) {
    let chipsList = calculateHeight()
    const len = chipsList.length
    const mousePosition = monitor.getClientOffset()
    for (let i = 0; i < len; i++) {
      const labelPosition = chipsList[i]
      const nextLabelPosition = i + 1 < len ? chipsList[i + 1] : null

      // 保证是在一行,区分多行的情况
      if (mousePosition.y > labelPosition.top && mousePosition.y < labelPosition.bottom) {
        const midLine = labelPosition.left + (labelPosition.right - labelPosition.left) / 2
        // 在标签中线前
        if (mousePosition.x < midLine) {
          showLineType.value = {
            index: i,
            location: 'left',
          }
          return showLineType.value
        }
        // 一行的最后一个(不在中线前 且 下一个标签的left<当前标签的right)
        if (
          nextLabelPosition &&
          mousePosition?.x > midLine &&
          nextLabelPosition.left < labelPosition.right
        ) {
          showLineType.value = {
            index: i,
            location: 'right',
          }
          return showLineType.value
        }
        // 最后一个
        if (mousePosition?.x > midLine && i === len - 1) {
          showLineType.value = {
            index: i,
            location: 'right',
          }
          return showLineType.value
        }
      }
    }
  }
}

我是将数据处理成了二维数组,每一行内折行都是一个子数组,从而每个子数组的高度都是一样的,把当前鼠标高度确定到每个子数组对应的高度,再在此子数组中找到对应横坐标位置,从而确定线应该出现在哪个位置

// 计算出一行内折行的高度数组
const calculateHeight = () => {
  const domItem: any = document.getElementsByClassName(props.from + 'ChipList')
  const domList: any = document.getElementsByClassName(props.from + 'ChipRow')[0]
  const len = domItem.length
  if (len === 0) return {}
  const curRowPosition = domList.getBoundingClientRect()
  const multiRowArr: any = [[]] // 同一大行的数据根据内部换行 形成一个二维数组,一维数组中的每一项为当前内部行
  let j = 0
  for (let i = 0; i < len; i++) {
    const labelPosition = domItem[i].getBoundingClientRect()
    const nextLabelPosition = i + 1 < len ? domItem[i + 1].getBoundingClientRect() : null
    multiRowArr[j].push(labelPosition)
    // 如果有下一项并且下一项的左侧<当前项的右侧 则认为换行,新增一个二维数组
    if (nextLabelPosition && nextLabelPosition.left < labelPosition.right) {
      multiRowArr[++j] = []
    }
  }
  // console.log('二维数组', multiRowArr)
  // 根据每一内部行的高度 生成一个对应的行高数组 每个index位置对应行数组中的一维的每行index
  const rowHeightArr: any = [] // 每一行的高度数组:[{top,bottom}]
  if (multiRowArr.length === 1) {
    rowHeightArr.push({
      top: curRowPosition.top,
      bottom: curRowPosition.bottom,
    })
  } else {
    const rowDiff = multiRowArr[1][0].top - multiRowArr[0][0].bottom
    // console.log('行中间查', rowDiff)
    for (let i = 0; i < multiRowArr.length; i++) {
      if (i === 0) {
        rowHeightArr.push({
          top: curRowPosition.top,
          bottom: multiRowArr[i][0].bottom + rowDiff / 2,
        })
      } else if (i === length - 1) {
        rowHeightArr.push({
          top: multiRowArr[i][0].top - rowDiff / 2,
          bottom: curRowPosition.bottom,
        })
      } else {
        rowHeightArr.push({
          top: multiRowArr[i][0].top - rowDiff / 2,
          bottom: multiRowArr[i][0].bottom + rowDiff / 2,
        })
      }
    }
  }
3、拖动放下时先删除上一位置的,再添加到放置位置(也可以反过来,先添加再删除)

因为上面获取到了对应的线出现的位置,可以根据线出现的位置判断放置到了哪里

来自哪里是通过 来自行 和 来自下标来判断的。( drop:(obj,monitor)=>{} 的obj即为drag过来的数据,里面可以放置来自的位置与来自的下标)文章来源地址https://www.toymoban.com/news/detail-489971.html

4、拖动预览始终在光标的中心

被拖动的元素比较大,你从元素的某个角拖动起来时拖动预览的位置 会离鼠标指针比较远
因此拖拽时想要让预览的标签位于鼠标正中心,就要通过getBoundingClientRect方法获取标签在页面上的位置:(但是不得不说,这个方法会导致页面进行重绘重排,是损耗性能的)
  const tagPosition: any = document.getElementById('dragPreview')
  const { height: tagHeight, width: tagWidth } = tagPosition.getBoundingClientRect()
  // 计算出标签中心点 与 开始拖动鼠标位置的 距离
  const leftOffset = tagWidth / 2 - (initialClientOffset.x - initialOffset.x)
  const topOffset = tagHeight / 2 - (initialClientOffset.y - initialOffset.y)
  const { x, y } = currentOffset
  const transform = `translate(${x - leftOffset}px, ${y - topOffset}px)`

到了这里,关于vue3自定义拖拽,vue3-dnd 的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue3使用拖拽组件draggable.next的使用教程【保姆级】

    环境:vue3+setup语法 首先放官方文档的链接: 中文版本: vue.draggable.next 中文文档 - itxst.com (民间翻译) 英文版本:GitHub - SortableJS/vue.draggable.next: Vue 3 compatible drag-and-drop component based on Sortable.js 因为自己写的过程中,官方文档和网上的资料都非常不明,使用版本各不相同,极

    2024年02月01日
    浏览(39)
  • 深入解析React DnD拖拽原理,轻松掌握拖放技巧!

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。。 本文作者:霁明 业务中会有一些需要实现拖拽的场景,尤其是偏视觉方向以及移动端较多。拖拽在一定程度上能让交互更加便捷,能

    2024年02月08日
    浏览(49)
  • react 基于 dnd-kit 封装的拖拽排序组件

    官网地址 https://docs.dndkit.com/introduction/installation 安装依赖 简单使用 建议直接看官网,已经描述得很详细了:https://docs.dndkit.com/presets/sortable 效果展示 注意事项 如果传入的是一个函数式组件,需要用一个html元素包裹住 这里的排序默认是读取 list 中的 id 作为 key 值的,如果

    2024年02月16日
    浏览(46)
  • 如何使用vue-smooth-dnd

    Vue Smooth DnD是一个基于Vue的平滑易用的拖放库。它提供了简单易用的API和可自定义的样式。 要使用Vue Smooth DnD,可以按照以下步骤进行操作: 安装Vue Smooth DnD 在组件中引入Vue Smooth DnD 在组件的template中使用     在这个例子中,我们使用了Vue Smooth DnD组件,并传入了一些属性和

    2024年02月11日
    浏览(41)
  • vue2&vue3 el-table实现整行拖拽排序功能(使用sortablejs插件)

    Vue3组件地址 Vue2组件地址 Vue2基于ElementUi再次封装基础组件文档 vue3+ts基于Element-plus再次封装基础组件文档

    2024年02月08日
    浏览(55)
  • vue3如何实现使用SortableJs插件进行表格内的数据项拖拽排序

    npm i sortablejs --save 或者 yarn add sortablejs   我使用的组件库是ant design vue, 我主要使用的是Sortablejs 中的结束拖拽之后的onEnd 属性,因为是单表格的简单拖拽排序所以只用到了自带的参数对象evt的oldIndex和newIndex这两个属性,分别表示被拖拽的数据项拖拽前的下标和拖拽完成之后

    2024年02月11日
    浏览(50)
  • vue3 - element-plus表格组件el-table实现鼠标拖曳排序功能,vue3 Table表格拖拽排序,表格每行使用鼠标拖动进行排序功能,表格拖拽排序实现(详细示例代码,一键复制开箱即用

    在vue3+elementPlus网站开发中,详细完成el-table表格的鼠标拖拽/拖曳/拖动排序,vue3使用element plus表格组件进行表格每行的拖动换位置排序功能(支持一键开启和关闭鼠标是否可拖动排序,代码易改造灵活),稍加改造可支持【树形复杂表格的排序】! 详细示例源代码,复制运行

    2024年04月09日
    浏览(58)
  • vue3 拖拽插件 Vue3DraggableResizable

    Vue3DraggableResizable 拖拽插件的官方文档 一、Vue3DraggableResizable 的属性和事件 属性 类型 默认值 功能描述 示例 initW Number null 设置初始宽度(px) Vue3DraggableResizable :initW=“100” / initH Number null 设置初始高度(px) Vue3DraggableResizable :initH=“100” / w Number 0 组件的当前宽度(px),你

    2024年02月03日
    浏览(40)
  • vue3鼠标拖拽滑动效果

    第一步 在utils下面新建一个directives.js文件,然后引入如下代码 第二步 在main.js中引入  第三步 页面直接使用即可

    2024年02月11日
    浏览(37)
  • vue3使用自定义组件内方法

    使用 defineExpose 来导出方法 script setup 组件时默认不导出属性方法的(类似 java 的 private ),即通过 ref 获取实例是无法访问到自定义的属性和方法,但是可以获取到组件实例。 可以通过 defineExpose 来指定要暴露的方法属性,便可以在外部访问到组件自定义的属性方法了。 当然也

    2024年01月19日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包