10分钟实现简易Vue拖拽排序

背景

平平无奇的一天,博主正在敲着自己的项目,这时候被领导告知要我写一个工程自动化工具,接到需求的时候我的大脑便开始疯狂转圈(因为我需要前后端都做,相对来说思考的东西要多一点),后来在思考数据设计,接口数据处理的一些细节之后,终于是有条不紊的进行了。
But ,一个Vue的拖拽排序功能如何实现却让我犯了难,在痛苦的学习中我终于明白了这个东西是怎么玩的 记录的同时进行分享,希望能帮助到有同样需求的码农们(狗头)

文章目录

  • 背景
  • 第一步是MDN拖拽方法总结,若无兴趣可以略过。
  • 一、官方关于拖拽方法和属性有哪些?
    • 1.可拖拽属性 draggable
    • 2.开始拖拽操作 dragstart
    • 3.拖拽数据 dataTransfer
    • 4.设置拖拽反馈图像 setDragImage
    • 5.拖拽效果 effectAllowed
    • 6.指定放置目标 dragover
    • 7.放置反馈 -moz-drag-over CSS
    • 8.执行放置 drop
    • 8.完成拖拽 dragend
  • 二、开始实现
    • 1.在总结完拖拽方法之后,拖拽-开始-进入-结束应当是正常流程
    • 2.渐入佳境
    • 3.打完收工
  • 总结
    • 明天,又是充满希望的一天!


第一步是MDN拖拽方法总结,若无兴趣可以略过。

本着做什么功能都尽量不导入包的思维,
我放弃了使用vue的插件(需要导入的包太大,冗余代码太多)
转而选择使用H5原生来实现这个功能


一、官方关于拖拽方法和属性有哪些?

首先让我们看一下MDN上对拖拽的方法的一些总结。

1.可拖拽属性 draggable

在 HTML 中,除了图像、链接和选择的文本默认的可拖拽行为之外,其他元素在默认情况下是不可拖拽的。要使其他的 HTML 元素可拖拽,
必须做三件事:
1.将想要拖拽的元素的 draggable 属性设置成 draggable="true"
2.为 dragstart () 事件添加一个监听程序
3.在上一步定义的监听程序中 设置拖拽数据

	<p draggable="true" 
	   ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
	  这个文本 <strong>可以strong> 被拖拽.
	p>

2.开始拖拽操作 dragstart

当用户开始拖拽时,会触发 dragstart ()。 事件
在这个例子中, dragstart ()。事件监听程序被添加到可拖拽元素本身;然而,你可以监听一个 祖先元素,因为就像大多数其他事件一样,拖拽事件会冒泡。
dragstart ()。事件中,你可以指定拖拽数据、反馈图像和拖拽效果,所有这些都将在下面描述。不过,我们只需要设置拖拽数据,因为在大多数情况下默认的图像和拖拽效果都是适用的。

	<p draggable="true" ondragstart="event.dataTransfer.setData('text/plain', 'This text may be dragged')">
	  这个文本 <strong>可以strong> 被拖拽.
	p>

3.拖拽数据 dataTransfer

所有 拖拽事件 都有一个名为 dataTransfer 的属性,它持有拖拽数据(dataTransfer 是一个 DataTransfer 对象)。
这个属性有两个参数,简而言之,第一个参数是你允许拖拽的类型,第二个是放置到指定链接的数据

	event.dataTransfer.setData("text/plain", "可拖拽的文本");
  • 你还可以提供多种类型的数据,代码如下:
	const dt = event.dataTransfer;
	dt.setData("application/x.bookmark", bookmarkString);
	dt.setData("text/uri-list", "http://www.abc.org");
	dt.setData("text/plain", "http://www.abc.org");
  • 如果你用相同格式重复添加,新数据会替换旧数据,你可以清除它们,代码如下:
	event.dataTransfer.clearData("text/uri-list");

4.设置拖拽反馈图像 setDragImage

当拖拽发生时,会生成拖拽目标的一个半透明图像(触发"{ event("dragstart")}" 事件的元素),并在拖拽过程中跟踪鼠标指针。这个图像是自动创建的,所以你不需要自己创建它。但是,你可以使用 setDragImage() 方法来自定义拖拽反馈图像。

	event.dataTransfer.setDragImage(image, xOffset, yOffset);
  • 这三个参数都是必要的。第一个是图像的引用。这个引用通常是一个 元素
    但也可以是 或任何其他元素。生成的反馈图像就是该图像在屏幕上的样子,
    以图像原始的大小绘制。
    setDragImage() 方法的第二、三个参数是图像位置相对于鼠标指针位置的偏移量。
    也可以使用不在文档中的图像和画布。这种技术在使用 canvas 元素绘制自定义的拖拽反馈图像时非常有用,如下面的例子:

    function dragWithCustomImage(event) {
      var canvas = document.createElementNS("http://www.w3.org/1999/xhtml","canvas");
      canvas.width = canvas.height = 50;
    
      var ctx = canvas.getContext("2d");
      ctx.lineWidth = 4;
      ctx.moveTo(0, 0);
      ctx.lineTo(50, 50);
      ctx.moveTo(0, 50);
      ctx.lineTo(50, 0);
      ctx.stroke();
    
      var dt = event.dataTransfer;
      dt.setData('text/plain', 拖拽的数据');
      dt.setDragImage(canvas, 25, 25);
    }
    

    在这个例子中,我们做了一个是画布的拖拽图像。当画布宽 50 像素,高 50 像素时,我们使用一半的偏移量(25 和 25),这样鼠标指针即为图像中心。

5.拖拽效果 effectAllowed

拖拽过程中可能会执行一些操作。 copy 操作用来指示被拖拽的数据将从当前位置复制到放置位置。 move 操作指示被拖拽的数据会被移动,link 操作表示在源和放置位置之间将会创建某种形式的关系或连接。
可以在 dragstart () 事件监听程序中设置 effectAllowed 属性以指定允许拖拽源头执行三种操作中的哪几种。

	event.dataTransfer.effectAllowed = "copy";
 	以上代码示例为只允许复制,你可以用不用的方式来实现你的需求:
	 1. none:不允许操作
	 2. copy:只复制
	 3. move:只移动
	 4. link:只链接
	 5. copyMove:复制或移动
	 6. copyLink:复制或链接
	 7. linkMove:链接或移动
	 8. all:复制,移动或链接

6.指定放置目标 dragover

dragenter ()dragover () 事件的监听程序用于表示有效的放置目标,也就是被拖拽项目可能放置的地方。网页或应用程序的大多数区域都不是放置数据的有效位置。因此,这些事件的默认处理是不允许放置。

如果你想要允许放置,你必须取消 dragenterdragover事件来阻止默认的处理。你可以在属性定义的事件监听程序返回 false,或者调用事件的 preventDefault() 方法来实现这一点。在一个独立脚本中的定义的函数里,可能后者更可行。

<div ondragover="return false">
<div ondragover="event.preventDefault()">

7.放置反馈 -moz-drag-over CSS

有几种方法可以向用户表明哪个位置允许放置。鼠标指针将根据 dropEffect 属性的值做必要的更新。鼠标指针具体的外观取决于用户平台,典型的如加号图标会出现在 ‘copy’ 中,而不允许放置时,会出现禁止放置的图标。在许多情况下,鼠标指针反馈就足够了。

但是,你还可以根据需要更新用户界面,如添加一个插入标记或使用高亮显示。对于简单的高亮显示,你可以在放置目标上使用 -moz-drag-over CSS 伪类。

.droparea:-moz-drag-over {
  border: 1px solid black;
}

注意:要使这个伪类生效,必须要在 dragenter 事件添加preventDefault()方法

8.执行放置 drop

当用户放开鼠标,拖放操作就会结束。

如果在有效的放置目标元素(即取消了 dragenter ()dragover () 的元素)上放开鼠标,放置会成功实现,drop () 事件在目标元素上被触发。否则拖拽会被取消,
不会触发 drop () 事件。

在所有拖拽操作相关的事件中,事件的 domxref("DragEvent.dataTransfer","dataTransfer") 属性会一直保存着拖拽数据。可使用 getData() 方法来取回数据。

function onDrop(event) {
  const data = event.dataTransfer.getData("text/plain");
  event.target.textContent = data;
  event.preventDefault();
}

8.完成拖拽 dragend

一旦拖拽完成,dragend () 事件会在拖拽源头(即触发 dragstart () 的元素)上发生。无论拖拽是成功还是被取消,这个事件都会被触发。然而,你可以使用 dropEffect 属性来决定执行什么放置操作。
如果在dragend () 事件中,dropEffect 属性值为 none,则拖拽会被取消。否则,这个属性会规定需要执行什么操作。源头元素可使用这个信息以在拖拽操作完成后从原来的位置移除被拖拽的项目。mozUserCancelled () 属性会在用户取消拖拽(按下 Esc 键)时设置为 true,在拖拽因为其他原因如无效放置目标等被取消时,或拖拽成功时,则设置为 false

放置可发生在同一窗口或另一个应用程序中。两种情况都会触发 dragend () 事件。事件的 screenXscreenY 属性会被设置为放置发生时鼠标在屏幕上的坐标。

event("dragend") 事件结束后,整个拖放操作就完成了。

二、开始实现

1.在总结完拖拽方法之后,拖拽-开始-进入-结束应当是正常流程

任何一个单独的事件都不可能完成我的业务需求,但是组合就可以,于是我用了以下事件

  1. dragstart 通过传给函数的index来知晓拖动的是哪个元素
  2. dragenter 通过index来知晓最后进入的是哪个元素
  3. dragend 元素放置时立马执行对应逻辑,将拖动元素的数据和最后放置元素的数据进行交换
  4. dragover 通过禁用父级的默认属性,来取消掉子元素的拖动禁止按钮

于是我添加了如下代码(示例):

	<view
		@dragstart="dragstart(index)"
		@dragenter.prevent="dragenter($event, index)"
		@dragend="dragend($event, index)"
	>
		拖拽测试代码
	</view>

对应函数为:

		dragstart(index) {
     
			console.log(index)
			this.startIndex = index;//存储开始拖动时候的下标,可以知道从哪个地方开始拖动的
		},
		dragenter(e, index) {
     
			console.log(e,index)
			this.endIndex = index;//存储结束拖动时候的下标,可以知道最后进入的是哪个元素
		},
		dragend(e, index) {
     
			console.log(e,index)
			let {
      startIndex, endIndex } = this; //有开始和结束直接把对应数据交换值就可以了
		},

值得注意的地方是,如果用第三方变量交换数据的时候,建议使用深拷贝给数据源进行复制,
Vue2.0的数组和对象的双向绑定一直是硬伤,
切记赋值的时候要用this.$set('必定有的数据','哪个属性','赋值数据源')

2.渐入佳境

值得欣慰的是现在简易版的基础拖动排序已经基本完成了,但这个时候出现一个问题:
在进行拖拽的时候,一直有一个禁用的图标显示,我意识到肯定是哪个地方做得不对了,经过多处查询文档,发现了出现这个的原因:
原来在拖拽的元素上,其父级必须要禁用默认阻止其放置的属性才会不出现这个禁止图标,
于是对代码进行修改,最后的代码如下所示:

<view  @dragover.prevent="allowDrop($event)">
	<view
		@dragstart="dragstart(index)"
		@dragenter.prevent="dragenter($event, index)"
		@dragend="dragend($event, index)"
	>
		拖拽测试代码
	</view>
</view>
		dragstart(index) {
     
			console.log(index)
			this.startIndex = index;//存储开始拖动时候的下标,可以知道从哪个地方开始拖动的
		},
		dragenter(e, index) {
     
			console.log(e,index)
			this.endIndex = index;//存储结束拖动时候的下标,可以知道最后进入的是哪个元素
		},
		dragend(e, index) {
     
			console.log(e,index)
			let {
      startIndex, endIndex } = this; //有开始和结束直接把对应数据交换值就可以了
		},
		allowDrop(e){
     
			e.preventDefault()  //禁用父级元素默认阻止放置的属性,使元素可进行放置(不出现禁用图标)
		},
		

3.打完收工

由于每人的业务需求不一样,具体的逻辑要自己写,成品实例如下图:


由于录制软件原因,拖动过程的动画被默认压缩了,显得动画不够流畅,这个没有办法,因为是白嫖的


总结

好记性不如烂笔头
随时随地给自己对项目的状态进行实时的记录,想来以后回忆起来也是极美的

明天,又是充满希望的一天!

最后放上一张镇楼图
在这里插入图片描述

你可能感兴趣的:(前端,性能优化,css,vue.js,javascript,html,html5,css3)