对于拖拽我们并不陌生,尤其是在 HTML5 支持拖拽后,让这一切变的更加容易了。本文旨在探讨 H5 拖拽方案、实现步骤,以及相关原理。
一、我理解的拖拽
为了更好的说明,这里我强行分类,大致如下:
操作类拖拽,譬如,拖拽上传;
功能性拖拽,拖拽排序,具有指向性,位置互换;
使用方便型,对某些固定元素,使其随处可放;
拖拽实现的思路都是一致的,唯一差别就是表现形式。
二、直接上手
让我们逐步深入,从示例开始吧(文中所用示例地址)!
1.首先,设置可拖动元素,我们使用 draggable
复制代码
为了方便下文具体说明,这里我添加了 id 和 ondragstart 事件,如下:
复制代码
ondragstart 属性调用了一个函数,drag(event),它规定了被拖动的数据。
2.然后,规定当元素被拖动时,会发生什么。关键词 setData()
function drag(ev)
{
ev.dataTransfer.setData('Text',ev.target.id);
}复制代码
dataTransfer.setData() 方法设置被拖数据的数据类型和值,后面我会详细说明。
这里需要注意 setData 的数据类型是 ‘Text’,值是可拖动元素的 id 。
3.最后,放到何处,ondragover 事件规定在何处放置被拖动的数据,当放置被拖数据时 ondrop 会被触发。
function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
function allowDrop(ev)
{
ev.preventDefault();
}复制代码
这里需要注意几个问题:
默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式,即通过调用 ondragover 事件的 event.preventDefault() 方法;
调用 preventDefault() 来避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开);
通过 dataTransfer.getData(‘Text’) 方法获得被拖的数据。该方法将返回在 setData() 方法中设置为相同类型的任何数据。
三、来回拖放
到这里我们已经可以将一个元素拖到指定容器中,那么如何让元素在两个容器中来回拖放呢?
继续保持之前的思路,我们做一点简单的调整:
1.首先,确保有俩元素;
.jartto1,
.jartto2 {
float: left;
width: 150px;
height: 140px;
margin: 10px;
border-radius: 3px;
border: 1px solid #aaaaaa;
}复制代码
2.更改 html 结构
draggable="true" οndragstart="drag(event)" />
3.触发方法
function allowDrop(ev) {
ev.preventDefault();
}
function drag(ev) {
ev.dataTransfer.setData('Text', ev.target.id);
}
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData('Text');
ev.target.appendChild(document.getElementById(data));
}复制代码
代码看着变化并不大,重点其实在这里:
draggable="true" οndragstart="drag(event)" />
复制代码
可以看到,我们对两个容器都设置了 ondrop 和 ondragover , 这样两个容器都允许放置,也就实现了来回拖放。
嗯,来回拖拽也实现了,我们继续升级,来看看随处拖放。这里稍微复杂一点,先简单梳理一下思路:
1.首先设置 dragover 事件作用于整个 document,确保元素可以随处放置;
2.在 dragstart 事件中计算鼠标拖拽的位置;
3.在 drop 事件中将上步计算的 offset 和 clientX,clientY 运算,计算出元素放置位置;
这里我以 Angularjs 举例说明,其他框架实现形式可能有差别,仅作为参考。
首先,创建指令,并设置作用元素的 draggable 属性;
angular.module('app')
.directive('dragdrop', [function () {
return {
link: (scope, element, attrs) => {
var el = element[0];
el.draggable = true;
}
};
}
]);复制代码
其次,存储拖拽数据;
el.addEventListener('dragstart',
function(e) {
let style = window.getComputedStyle(e.target, null);
e.dataTransfer.setData('text/plain',
`${(parseInt(style.getPropertyValue('left')) - e.clientX)},${(parseInt(style.getPropertyValue('top')) - e.clientY)}`
);
},false);复制代码
然后,我们必须阻止对元素的默认处理方式,从而使元素可以放置到其他元素中;
document.body.addEventListener('dragover',function(e) {
e.preventDefault();
return false;
},false);复制代码
再次,将拖拽数据取出,重新确定元素位置;
document.body.addEventListener('drop',function(e) {
let offset = e.dataTransfer.getData('text/plain').split(',');
el.style.left = (e.clientX + parseInt(offset[0])) + 'px';
el.style.top = (e.clientY + parseInt(offset[1])) + 'px';
e.preventDefault();
return false;
},false);复制代码
最后,我们可以这样使用:
其实我们还可以在拖拽的过程中做一些样式处理(加类减类),以获取更好的用户体验效果,但这并不是必要的,所以代码中并没有体现。
当然,我们也可以通过 mouse 事件来实现,如下:
element.on('mousedown', function(event) {
// Prevent default dragging of selected content
event.preventDefault();
startX = event.pageX - x;
startY = event.pageY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});复制代码
这里只展示了代码片段,详情请查看,demo6 示例代码
五、细节问题处理
整个过程看起来相当顺利,其实还是有很多细节问题的,这里简单罗列一下:
第一,fix 定位的元素,拖拽会失效;
第二,容器存在包含关系,除非你要 drag everywhere,否则拖拽将失效;
第三,需要阻止浏览器的默认行为,譬如重定向;
第四,默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式,即通过调用 ondragover 事件的 event.preventDefault() 方法;
第五,移动兼容较差,慎重选择;
六、drag 和 drop 事件
我们列举一下 drag 和 drop 事件,意思很明了,这里就不赘述了。
drag
dragstart
dragend
dragover
dragenter
dragleave
drop
我们在 drag everywhere 例子中使用到了 DataTransfer.setData() ,再熟悉一下。DataTransfer.setData() 方法用来设置拖放操作的 drag data 到指定的数据和类型。
如果给定类型的数据不存在,则将其添加到拖动数据存储的末尾,使得 types 列表中的最后一个项目将是新类型。
如果给定类型的数据已经存在,现有数据将被替换为相同的位置。也就是说,替换相同类型的数据时 types列表的顺序不会更改。
示例数据类型为:"text/plain" 和 "text/uri-list"
语法:
void dataTransfer.setData(format, data);复制代码
参数:
format: 一个DOMString 表示要添加到 drag object的拖动数据的类型。
data: 一个 DOMString表示要添加到 drag object的数据。
八、Window.getComputedStyle()
除了 DataTransfer.setData() 这里我还要介绍一个特别有意思的属性,Window.getComputedStyle(),为什么这么说呢?先来看一下他的文档:
Window.getComputedStyle() 方法给出应用活动样式表后的元素的所有CSS属性的值,并解析这些值可能包含的任何基本计算。
语法:
let style = window.getComputedStyle(element, [pseudoElt]);复制代码
参数:
element:用于获取计算样式的Element
pseudoElt:可选,指定一个要匹配的伪元素的字符串。必须对普通元素省略(或null)。
强大之处就在于:它返回的样式是一个实时的 CSSStyleDeclaration 对象,当元素的样式更改时,它会自动更新本身。
至于兼容性,请看下图:
九、getPropertyValue
The CSSStyleDeclaration.getPropertyValue() method interface returns a DOMString containing the value of a specified CSS property.
语法:
var value = style.getPropertyValue(property);复制代码
示例:
var declaration = document.styleSheets[0].cssRules[0].style;
var value = declaration.getPropertyValue('margin'); // "1px 2px"复制代码
额外扩展了一个属性和两个方法,无非想提供更多的实现思路,从而使我们的开发更加高效。这不是考点,考试不会考哦~
十、插件推荐
在自己尝试实现 drag 之前很长一段时间,我都在使用一些成熟插件,这里推荐几个:
类似的插件网上一搜一大堆,前端大军造轮子的能力还是很强的,这不,我们今天又造了一个🙈~
十一、参考