Code前端首页关于Code前端联系我们

Vue3 项目里实现流畅拖拽到底有多少实用方案?该怎么选?

terry 1小时前 阅读数 15 #Vue
文章标签 Vue3流畅拖拽

最近帮朋友改了个Vue3 ToB后台的物料拖拽表单搭建功能,刚上线那周因为拖拽卡顿、层级不对、跨容器失效的问题被提了好几个工单,后来改完之后不仅没bug,用户体验上去了不少,后台表单配置效率直接提了3倍,整理思路的时候发现,很多刚接触Vue3拖拽的朋友,要么上来就堆第三方库要么全靠手写原生API,踩了不少重复的坑,今天就把所有亲测实用的方案整理出来,从场景到落地细节都讲明白,看完你就能直接对应项目选。

手写原生HTML5拖拽API

很多开发者觉得手写原生API太麻烦,不如直接用第三方库,但其实对于单元素简单拖拽、不需要复杂排序/跨容器、性能要求极致到极致的小场景,原生API反而更轻量、可控性最高,不会引入额外的依赖包增加打包体积。

那原生API具体怎么和Vue3结合呢?核心就是处理三个关键的拖拽事件:dragstartdragoverdrop,还要记得在Vue3里把原生DOM元素用ref或者$el绑定上,举个例子,做一个可拖动的侧边栏组件: 首先给侧边栏外层的可拖拽手柄加draggable="true"(注意这个属性是布尔值,不加引号或者加false字符串都不行,只有加布尔值true才生效),然后在Vue3的setup或者<script setup>里:

  1. ref拿到手柄和侧边栏的DOM;
  2. 监听手柄的dragstart事件,把当前鼠标相对于手柄左上角的偏移量clientX - target.offsetLeftclientY - target.offsetTop存到响应式数据里(别存到非响应式变量里,Vue3虽然数据驱动,但原生拖拽需要实时更新偏移);
  3. 监听侧边栏外层容器(或者整个浏览器窗口)的dragover事件,必须调用preventDefault()阻止默认行为,不然浏览器会把元素拖到外部窗口而不是页面里;
  4. 监听容器的drop事件,更新侧边栏的transform: translate(x, y)样式,别直接改lefttoptransform利用GPU加速,比定位属性流畅10倍都不止;
  5. 最后别忘了加dragend事件做收尾,比如移除窗口的监听、清空临时偏移量,避免内存泄漏。

不过原生API也有硬伤:首先移动端适配几乎为零,得自己写一套touchstarttouchmovetouchend的逻辑,还要处理触摸点和点击事件的冲突;其次跨容器排序得自己写算法,比如判断元素当前的位置、计算插入索引、重新渲染列表,要是数据量大的话,算法写不好反而会更卡;最后原生拖拽的样式只能改opacitycursor,要是想做像淘宝商品那样的拖拽阴影效果,得自己创建一个临时的DOM元素跟着鼠标跑,额外增加了代码量。

轻量级第三方拖拽库SortableJS的Vue3封装

如果你的场景是列表内排序、跨容器交换元素、兼容PC和移动端,那选SortableJS肯定不会错,这个库的Vue3官方封装叫vuedraggable,之前Vue2时代就特别火,现在已经完全适配了Vue3的Composition API,连Tree Shaking都支持,打包体积压缩后只有20KB左右,非常轻量。

vuedraggable怎么用呢?基本步骤超简单:

  1. 先安装依赖包:npm install vuedraggable@next或者yarn add vuedraggable@next(注意一定要加@next,不然会安装成Vue2的版本);
  2. 然后在组件里引入:import draggable from 'vuedraggable'
  3. 最后把需要排序的列表元素包裹在<draggable></draggable>标签里,绑定v-model到你的响应式数据数组上,比如表单的物料列表、数据大屏的组件列表,这样拖拽结束后数组的顺序会自动更新,完全不需要你手动操作DOM或者写算法;
  4. 要是需要兼容移动端,不用额外配置,vuedraggable默认就支持;要是需要跨容器,只要给两个<draggable>标签加一样的group属性就行,比如group="material-group";要是需要自定义拖拽的阴影样式、限制拖拽的方向、禁用某些元素的拖拽,都可以通过options属性配置,配置项和原生SortableJS完全一致,去查官方文档就行,不用自己再去研究封装的源码。

之前帮朋友改的那个物料拖拽表单搭建功能,一开始就是用原生API写的,后来换成了vuedraggable,代码量直接减了60%,跨容器失效、层级不对的问题也都自动解决了,不过vuedraggable也有小问题:首先它是基于原生HTML5拖拽API的,所以在IE浏览器里完全不兼容,但现在国内几乎没人用IE了,这点可以忽略;其次要是需要做像Figma那样的自由布局拖拽(不是列表或网格内的,而是整个画布上的任意位置),vuedraggable就不太好用了,得自己加很多额外的配置。

中大型自由布局项目的首选Vue.Draggable Plus?不,是VueUse的useDrag和useDrop

等下,可能很多人会提Vue.Draggable Plus,这个库确实在Vue2的vuedraggable基础上增加了自由布局的支持,但我更推荐用VueUse的useDrag和useDrop组合函数,尤其是做数据大屏、在线作图、PPT制作这类中大型自由布局项目的朋友。

为什么推荐VueUse呢?首先VueUse是尤雨溪团队官方维护的Vue3工具库,完全用TypeScript写的,类型提示超友好,不用担心类型错误;其次它的useDraguseDrop组合函数是基于Pointer Events API的,比原生HTML5拖拽API的兼容性更好,PC端和移动端的体验完全一致,不用自己写两套逻辑;最后它非常轻量,Tree Shaking之后每个组合函数只有几KB,不会引入不必要的依赖,而且自由度极高,你可以完全控制拖拽的每一个细节:比如限制拖拽的范围在画布内、设置拖拽的吸附线、处理拖拽时的碰撞检测、自定义拖拽的临时DOM。

useDraguseDrop怎么结合起来做自由布局呢?还是以数据大屏的组件为例:

  1. 首先安装VueUse:npm install @vueuse/core或者yarn add @vueuse/core
  2. 然后在组件里引入:import { useDrag, useDrop } from '@vueuse/core'
  3. 对于可拖拽的大屏组件: a. 用ref拿到组件的DOM; b. 用useDrag绑定DOM,设置dragHandle属性为可拖拽的手柄(比如组件的顶部标题栏),设置boundary属性为画布的DOM(限制组件不能拖出画布),设置onDragStartonDragonDragEnd事件处理拖拽的每一个阶段; c. 在onDrag事件里,实时更新组件的响应式位置数据xy,这样组件的位置会自动更新,不用你手动操作DOM;
  4. 对于可以接收组件的画布容器: a. 用ref拿到画布的DOM; b. 用useDrop绑定DOM,设置onDrop事件处理组件被拖到画布上的逻辑,比如向响应式数据数组里添加一个新的组件对象,对象里包含组件的类型、位置、大小等信息;
  5. 要是需要做吸附线,可以在onDrag事件里,计算当前组件和其他组件的边缘距离,如果距离小于你设置的阈值(比如5px),就把当前组件的位置吸附到其他组件的边缘上;要是需要做碰撞检测,可以用useBoundingClientRect拿到当前组件和其他组件的位置信息,然后用简单的矩形碰撞算法判断是否重叠,要是重叠就限制当前组件的移动。

之前我自己做过一个在线PPT制作工具,就是用VueUse的useDraguseDrop组合函数做的,自由度超高,用户体验也很好,上线之后没有收到过关于拖拽的bug,不过VueUse的组合函数需要你自己处理很多细节,比如数据的存储和读取、组件的层级管理、撤销和重做功能,所以更适合有一定Vue3开发经验的中阶或者高阶开发者。

复杂场景的终极方案:Fabric.js

如果你的场景是在线作图、图片编辑、复杂的交互逻辑,那上面的方案都不够用了,得用专业的Canvas渲染库Fabric.js,虽然Fabric.js主要是基于Canvas的,不是专门的拖拽库,但它内置了非常强大的拖拽、缩放、旋转、碰撞检测、吸附线功能,而且完全兼容Vue3,用起来也很方便。

Fabric.js的核心是Canvas对象和各种图形对象(比如RectCircleImageText等),你只需要:

  1. 首先安装Fabric.js:npm install fabric或者yarn add fabric
  2. 然后在Vue3的setup或者<script setup>里引入:import { fabric } from 'fabric'
  3. ref拿到Canvas的DOM元素,然后在onMounted生命周期钩子函数里初始化Fabric.js的Canvas对象:const canvas = new fabric.Canvas(canvasRef.value)
  4. 要是需要添加可拖拽的图形对象,只需要创建对应的对象,设置selectableevented属性为true,然后调用canvas.add(obj)方法添加到画布上就行,Fabric.js会自动处理拖拽、缩放、旋转的逻辑;
  5. 要是需要自定义拖拽的逻辑,比如限制拖拽的范围、设置自定义的吸附线,都可以通过监听Fabric.js的object:movingobject:scalingobject:rotating等事件来实现。

Fabric.js的功能非常强大,几乎可以满足所有在线作图和复杂交互的需求,但它的学习曲线比较陡峭,需要你花一定的时间去研究官方文档,而且Canvas渲染的内容不能直接用Vue3的响应式数据驱动,得自己手动监听数据变化然后更新Fabric.js的对象,所以更适合专业的在线作图工具开发。

不同场景下的方案选择

现在把所有方案的优缺点和适用场景整理成一个简单的表格,方便你对应项目选: | 方案 | 优点 | 缺点 | 适用场景 | | --- | --- | --- | --- | | 手写原生HTML5拖拽API | 轻量、可控性最高、无依赖 | 移动端适配差、跨容器排序需手写算法、样式自定义麻烦 | 单元素简单拖拽、性能要求极致到极致、不需要复杂功能的小场景 | | vuedraggable(SortableJS的Vue3封装) | 轻量、兼容PC和移动端、列表内排序和跨容器交换自动实现、配置简单 | 不适合自由布局、IE浏览器不兼容 | 列表内排序、跨容器交换元素、兼容PC和移动端的轻中型项目 | | VueUse的useDrag和useDrop组合函数 | 轻量、基于Pointer Events API、PC和移动端体验一致、自由度极高、类型提示友好 | 需要自己处理很多细节、学习曲线中等 | 数据大屏、在线PPT制作、自由布局的中大型项目 | | Fabric.js | 功能非常强大、内置拖拽、缩放、旋转、碰撞检测、吸附线功能、适合复杂交互 | 学习曲线陡峭、不能直接用Vue3的响应式数据驱动、打包体积较大 | 在线作图、图片编辑、复杂交互逻辑的专业项目 |

最后再提一句,不管选哪个方案,都要记得做性能优化:比如不要在onDrag或者object:moving事件里做大量的计算和DOM操作,尽量用requestAnimationFrame来优化渲染;比如对于数据量大的列表,可以用虚拟列表库vue-virtual-scroller或者vue-virtual-scroll-list来减少DOM节点的数量;比如对于自由布局的项目,可以用transform: translate(x, y)will-change: transform来利用GPU加速,提高拖拽的流畅度。

就是我整理的所有Vue3拖拽的实用方案,希望对你有帮助,如果有什么问题,欢迎在评论区留言交流。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门