HTML5魔法堂:全面理解Drag & Drop API
html代码片断
复制代码
<p id="dialog">
<p id="title">
Hi there!
</p>
<p id="content">
Welcome here every one. We would learn the HTML5 feature DnD API now!
<p>
</p>
复制代码
js代码片断
DnD && DnD(document.getElementById('title'), document.getElementById('dialog'));
DnD.js工具库
复制代码
;(function(exports, contains){
var evtPrefix = '',
off = 'removeEventListener',
on = 'addEventListener' in document && 'addEventListener' ||
(evtPrefix = 'on', off = 'detachEvent') && 'attachEvent';
var docEl = document.documentElement,
body = document.body;
var css = function(el, prop, expectedVal){
var val;
if (el.currentStyle){
val = el.currentStyle[prop];
}
else{
val = window.getComputedStyle(el, null)[prop];
}
if (arguments.length === 3){
if (typeof expectedVal === 'string')
return val === expectedVal;
if (Object.prototype.toString.call(expectedVal) === '[object RegExp]')
return expectedVal.test(val);
return false;
}
return val;
};
// 修正获取元素离页面左上角距离的bug
var props = [['marginLeft', 'marginTop'], ['borderLeft', 'borderTop'], ['left', 'top']];
var getOffsetXY = function(el){
var oxy = {ox: el.offsetLeft, oy: el.offsetTop};
if (el.offsetParent){
var poxy = getOffsetXY(el.offsetParent);
oxy = {ox: oxy.ox + poxy.ox, oy: oxy.oy + poxy.oy};
}
else if (css(el, 'position', /relative|absolute/i)){
for (var i = 0, prop; prop = props[i++];){
oxy = {
ox: oxy.ox + (parseInt(css(el, prop[0])) || 0),
oy: oxy.oy + (parseInt(css(el, prop[1])) || 0)
};
}
}
return oxy;
};
var getPointXY = function(evt){
if ('pageX' in evt){
return {
x: evt.pageX,
y: evt.pageY
};
}
else{
return {
x: evt.clientX + (docEl && docEl.scrollLeft || body && body.scrollLeft || 0) - (docEl && docEl.clientLeft || body && body.clientLeft || 0),
y: evt.clientY + (docEl && docEl.scrollLeft || body && body.scrollTop || 0) - (docEl && docEl.clientLeft || body && body.clientTop || 0)
};
}
};
exports.DnD = function(dragEl, addedEl){
if (!this instanceof DnD){
return new DnD(dragEl, addedEl);
}
var dragEls = [];
if (contains(dragEl, addedEl)){
dragEls.push({
el: dragEl,
ox: 0,
oy: 0
});
}
else if (contains(addedEl, dragEl)){
dragEls.push({
el: addedEl,
ox: 0,
oy: 0
});
}
else{
dragEls = [{
el: addedEl,
ox: 0,
oy: 0
},{
el: dragEl,
ox: 0,
oy: 0
}];
}
var ox, oy;
dragEl[on](evtPrefix + 'mousedown', function(evt){
evt = evt || window.event;
var pointXY = getPointXY(evt);
ox = pointXY.x;
oy = pointXY.y;
for (var i = 0, currEl; currEl = dragEls[i++];){
var oxy = getOffsetXY(currEl.el);
currEl.ox = oxy.ox;
currEl.oy = oxy.oy;
currEl.el.style.position = 'absolute';
currEl.el.style.left = currEl.ox + 'px';
currEl.el.style.top = currEl.oy + 'px';
}
var onDOCMousemove = function(evt){
evt = evt || window.event;
var dx = evt.clientX - ox;
var dy = evt.clientY - oy;
for (var i = 0, currEl; currEl = dragEls[i++];){
currEl.el.style.left = (currEl.ox + dx) + 'px';
currEl.el.style.top = (currEl.oy + dy) + 'px';
}
};
document[on](evtPrefix + 'mousemove', onDOCMousemove);
document[on](evtPrefix + 'mouseup', function(evt){
evt = evt || window.event;
document[off](evtPrefix + 'mousemove', onDOCMousemove);
});
});
};
}(window, window.contains));
复制代码
contains.js工具库
复制代码
;(function(exports){
exports.contains = function(pel, cel){
// ie
if (pel.contains){
return pel.contains(cel);
}
else if(pel.compareDocumentPosition){
return !!pel.compareDocumentPosition(cel) & 16;
}
else{
var p;
while ((p = cel.parentNode) && p.nodeType === 1){
if (pel === p) return true;
}
return true;
}
};
}(window));
HTML5实现简单拖拽
功能:实现在p#title上按下鼠标左键并移动鼠标时,拖拽整个p#dialog,但释放鼠标时停止拖拽。下面的例子仅能在FF下运行
html代码片段
复制代码
<p id="dialog">
<p id="title" draggable="true">
Hi there!
</p>
<p id="content">
Welcome here every one. We would learn the HTML5 feature DnD API now!
<p>
</p>
复制代码
js代码片段
DnD && DnD(document.getElementById('title'), document.getElementById('dialog'));
DnD.js工具库
复制代码
;(function(exports, contains){
var css = function(el, prop, expectedVal){
var val = window.getComputedStyle(el, null)[prop];
if (arguments.length === 3){
if (typeof expectedVal === 'string')
return val === expectedVal;
if (Object.prototype.toString.call(expectedVal) === '[object RegExp]')
return expectedVal.test(val);
return false;
}
return val;
};
var props = [['marginLeft', 'marginTop'], ['borderLeft', 'borderTop'], ['left', 'top']];
var getOffsetXY = function(el){
var oxy = {ox: el.offsetLeft, oy: el.offsetTop};
if (el.offsetParent){
var poxy = getOffsetXY(el.offsetParent);
oxy = {ox: oxy.ox + poxy.ox, oy: oxy.oy + poxy.oy};
}
else if (css(el, 'position', /relative|absolute/i)){
for (var i = 0, prop; prop = props[i++];){
oxy = {
ox: oxy.ox + (parseInt(css(el, prop[0])) || 0),
oy: oxy.oy + (parseInt(css(el, prop[1])) || 0)
};
}
}
return oxy;
};
var setXY = function(e, dragEls, ox, oy){
var dx = e.clientX - ox;
var dy = e.clientY - oy;
dragEls.forEach(function(item, index, dragEls){
item.el.style.left = (item.ox + dx) + 'px';
item.el.style.top = (item.oy + dy) + 'px';
});
};
exports.DnD = function(dragEl, addedEl){
if (!this instanceof DnD){
return new DnD(dragEl, addedEl);
}
var dragEls = [], addElements = [];
if (contains(dragEl, addedEl)){
dragEls.push({
el: dragEl,
ox: 0,
oy: 0
});
}
else if (contains(addedEl, dragEl)){
dragEls.push({
el: addedEl,
ox: 0,
oy: 0
});
}
else{
dragEls = [{
el: addedEl,
ox: 0,
oy: 0
},{
el: dragEl,
ox: 0,
oy: 0
}];
}
var ox,oy;
dragEl.addEventListener('dragstart', function(e){
e.dataTransfer.setData('Text', '');
e.dataTransfer.setDragImage(document.createElement('p'), 0, 0);
e.dataTransfer.effectAllowed = 'move';
if (ox == null){
ox = e.pageX;
oy = e.pageY;
dragEls.forEach(function(item,index,dragEls){
var oxy = getOffsetXY(item.el);
item.ox = oxy.ox;
item.oy = oxy.oy;
item.el.style.position = 'absolute';
item.el.style.left = item.ox + 'px';
item.el.style.top = item.oy + 'px';
});
// 由于dragover是在拖动一段距离后才会触发,从而导致被拖动的元素出现突然开始移动的效果
// 通过在dragstart中修改被拖动元素offsetTop/Left可优化该情况
setXY(e, dragEls, ox, oy);
}
}, false);
document.addEventListener('dragover', function(e){
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
setXY(e, dragEls, ox, oy);
}, false);
document.addEventListener('drop', function(e){
e.preventDefault();
e.stopPropagation();
}, false);
};
}(window, window.contains));
复制代码
contains.js工具库与上一节的相同
如何启用DnD效果
html片段
<p id="drag" draggable="true" style="width:100px;height:50px;background-color:red;">
test
</p>
js片段
复制代码
var drag = document.getElementById('drag');
drag.onselectstart = function(){return false;};
// FF下拖拽时,默认不会生成一个被拖拽元素的阴影并跟随鼠标移动
// 需通过e.dataTransfer.setData来启动该效果
drag.ondragstart = function(e){
e.dataTransfer.setData('text', e.target.innerHTML);
};
复制代码
关键点:
1. 为触发拖拽的元素添加 draggable="true" 特性,用于启动HTML5的DnD功能(即元素的 dragstart 事件可被触发);
2. 在FF下即使添加 draggable="true" 特性,但仅仅会触发 dragstart 事件,但DnD功能并没有被完全打开(拖拽元素时没有任何视觉效果),需要调用 event.dataTransfer.setData('Text','') 彻底开启DnD功能。
3. 在Safari4下则需要借助CSS规则来启动DnD功能, [draggable=true]{ -webkit-user-drag: element; }
draggable属性详解
作用:用于指定标签是否可被拖拽
属性值范围如下:
1. true ,表示可被拖拽
2. false ,表示不可被拖拽
3. auto ,默认值,img和带href属性的a标签则表示可拖拽,其他标签表示不可被拖拽
4. 其他值,表示不可被拖拽
、DnD生命周期
1. 被拖拽元素的生命周期
dragstart :当被拖拽元素开始被拖拽时触发
注意:
[a]. event.dataTransfer的大部分设置均在这里配置
[b]. 若调用event.preventDefault()则会阻止拖拽行为,导致后续的拖拽事件不被触发
[c]. 触发dragstart事件后,其他元素的mousemove,mouseover,mouseenter,mouseleave,mouseout事件均不会被触发了
drag :当被拖拽元素被拖拽时触发
dragend :当拖拽行为结束后触发
2. 目标元素的生命周期
dragenter :当被拖拽元素进入目标元素时触发
dragover :当被拖拽元素在目标元素上移动时触发
注意:
[a]. 可以在这里设置dropEffect的值,事件的默认行为是将dropEffect设置为none
[b]. 该事件是被拖拽元素在目标元素上移动一段时间后才触发
[c]. 事件的默认行为是不允许被拖拽元素在其他元素上释放或放置(即无法触发 drop 事件),需要通过 event.preventDefault() 来阻止默认行为才能触发后续的 drop 事件。
drop :当被拖拽元素在目标元素上,而且释放鼠标左键时触发
注意:
[a]. 对于外来的被拖拽元素(超链接、文件、图片源), drop 事件的默认行为是浏览器将当前页面重定向到被拖拽元素所指向的资源上
[b]. 对文档内部的被拖拽元素,IE10+和Chrome下的默认行为是不作为,而FF得默认行为是新打开一个文档用于访问被拖拽元素所指向的资源
示例代码:
复制代码
<p id="drag" style="width:50px;height:50px;background-color:red;">Test</p>
<p id="drop" style="width:100px;height:100px;border:solid 1px red;"></p>
<script type="text/javascript">
var drag = document.getElementById('drag'), drop = document.getElementById('drop');
drag.ondragstart = function(evt){
evt.dataTransfer.setData('Text', 'www.baidu.com');
};
drop.ondragover = function(evt){
evt.preventDefault(); // 这样才能触发drop的drop事件
};
</script>
复制代码
3. 整体生命周期
dragstart -> drag -> dragenter -> dragover -> drop -> dragend
八、DnD中最重要的数据传递对象──DataTransfer对象
DataTransfer对象用于在配置拖拽行为效果,并且在拖拽过程的各事件间传递数据信息。它存储在事件对象当中,下面我们逐步了解它吧。
1. [object DragEvent]对象
继承自 [object MouseEvent] 对象,其实就多了个 {DataTransfer} dataTransfer 属性
2. [object DataTransfer]对象详解
上文说到DataTransfer对象可用于传递数据信息,而数据信息的数据类型被限定为字符串和文件类型
2.1. effectAllowed 和 dropEffect 属性
这个两个属性对于初次接触DnD的朋友来说,可谓最令人摸不着头脑的,网上和各书籍上对这两个属性的解释均不全面,下面我试图尽量把它们讲明白。
effectAllowed 和 dropEffect 最主要的作用是,用于配置拖拽操作过程中鼠标指针的类型以便提示用户后续可执行怎样的操作;其次的作用是,控制 drop 事件的触发与否。
[a] effectAllowed
作用:用于设置被拖拽元素可执行的操作。
取值范围:
copy ,限定dropEffect的属性值为copy,否则会鼠标指针为禁止样式
link ,限定dropEffect的属性值为link,否则会鼠标指针为禁止样式
move ,限定dropEffect的属性值为move,否则会鼠标指针为禁止样式
copyLink ,限定dropEffect的属性值为copy和link,否则会鼠标指针为禁止样式
copyMove ,限定dropEffect的属性值为copy和move,否则会鼠标指针为禁止样式
linkMove ,限定dropEffect的属性值为link和move,否则会鼠标指针为禁止样式
all ,允许dropEffect的属性值为任意值
none ,鼠标指针一直为禁止样式,不管dropEffect的属性值是什么
uninitialized ,没有限定dropEffect属性的值,效果和 all 一样。
注意:仅能在 dragstart 事件中设置该属性,其他事件中设置均无效。
[b]. dropEffect
作用:用于设置目标元素将执行的操作,若属性值属于 effectAllowed 范围内,则鼠标指针将显示对应的指针样式,否则则显示禁止的指针样式。
取值范围:
copy :被拖拽元素将被复制到目标元素内,若属于 effectAllowed 范围内时,则鼠标指针显示复制的样式,否则则显示禁止的指针样式。
link :被拖拽元素将以超链接的形式打开资源(具体是否打开资源请参考七、2),若属于 effectAllowed 范围内时,则鼠标指针显示超链接的样式,否则则显示禁止的指针样式。
move :被拖拽元素将被移动到目标元素内,若属于 effectAllowed 范围内时,则鼠标指针显示移动的样式,否则则显示禁止的指针样式。
none :被拖拽元素不能在目标元素上作任何操作,一直显示禁止的指针样式。除了文本框外其他元素的默认值均为none
注意:
1. 仅能在 dragover 事件中设置该属性值,其他事件中设置均无效
2. 当显示禁止的指针样式时,将无法触发目标元素的 drop 事件。
上一篇: JAVAEE 实训日志 一
下一篇: 2020-12-28