1. 拖放的基本概念
拖(drag)放(drop)是 HTML5 标准的组成部分,拖放是一种常见的特性,即抓取对象以后拖到另一个位置,任何元素都可以被拖放。
**
2. 拖放的几个事件
下面举一个简单的栗子来详细说明拖放的常用事件及其应用:
需求说明:如上图,这是一个常见的三栏布局,要求图中箭头部分可上下、左右拖动,使得三个box可以根据用户需要调整内容的长宽,从而扩大缩小展示区域。
特殊说明:以下代码片段均为react的伪代码,不保证正常运行哦。
第一步:骨架搭建
很明显的布局方式就是利用css3的Flex(弹性布局)来搭建,代码如下
<style>
.container{ width: 100%; height: 100vh; display: flex; flex-direction: row; }
.panel-left{ width: 200px; position: relative; }
.left-move-bar{ width: 6px; height: 100%; position: absolute; top: 0; right: -6px;
background-color: '#eee'; cursor: 'ew-resize'; }
.panel-right{ flex: 1; display: flex; flex-direction: column; }
.panel-right-body{ flex: 1; }
.panel-right-bottom{ position: relative; height: 400px }
.bottom-move-bar{ width: 100%; height: 6px; position: absolute; top: 0; left: 0;
background-color: '#eee'; cursor: 'ew-resize'; }
</style>
<div className="container">
<div className="panel-left" style={{ width: this.state.leftWidth }}>
<div className="left-move-bar"></div> // 可左右拖动bar
</div>
<div className="panel-right">
<div className="panel-right-body"></div>
<div className="panel-right-bottom" style={{ height: this.state.bottomHeight }}>
<div className="bottom-move-bar"></div> // 可上下拖动bar
</div>
</div>
</div>
第二步:处理被拖对象
设置被拖元素为可拖放
**
好了,我们的代码布局已经写好了,接下来就要一步步实现拖拽了;首先,为了使元素可拖动,把被拖对象的draggable 属性设置为 true:
<div class="left-move-bar" draggable="true"></div>
被拖对象事件监听
<div
class="left-move-bar"
draggable="true"
onDragStart={handleLeftBarDragStart}>
</div>
<div
class="bottom-move-bar"
draggable="true"
onDragStart={handleBottomBarDragStart}>
</div>
handleDragStart方法如下,主要是获取被拖对象的鼠标位置
handleLeftBarDragStart = (e) => {
this.startX = e.clientX; // 获取鼠标起点位置
e.dataTransfer.setData('target', 'x'); // 设置传输数据,用于区分拖拽的是横向bar还是竖向bar
}
handleBottomBarDragStart = (e) => {
this.startY = e.clientY;
e.dataTransfer.setData('target', 'y');
}
ondragstart 表示被拖对象开始拖放时触发,全程只执行一次。
此外,还有2个不太常用的事件对象(存在兼容性问题,结尾有说明):
ondrag 表示被拖对象处于拖动过程中触发,全程一直触发,直到被成功放置。
ondragend 表示被拖对象被成功放置后触发,全程只执行一次
第三步:处理目标对象
允许放置
**
默认地,我们是无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式。这就要通过调用 ondragover 事件的 event.preventDefault() 方法:
<div class="panel-left" onDragOver={e => e.preventDefault()}></div>
<div class="panel-right" onDragOver={e => e.preventDefault()}></div>
ondragover 被拖对象接触到目标对象开始触发,只要不停止拖放动作,就会一直触发。
注意:因为我们拖动bar的可拖动范围是全屏幕,所以必须把左侧box和右侧box都要阻止默认事件
此外,还有2个过程对象的事件也可以了解一下:
ondragenter 被拖对象进入到目标对象触发,只执行一次。
ondragleave 被拖对象离开目标对象以后触发,全程一直执行,直到拖放结束。
进行放置
**
当放置被拖对象时,会发生 ondrop 事件,全程只执行一次。
<div class="panel-left" onDragOver={e => e.preventDefault()} onDrop={handleOnDrop}></div>
<div class="panel-right" onDragOver={e => e.preventDefault()} onDrop={handleOnDrop}></div>
handleOnDrop = (e) => {
const target = e.dataTransfer.getData('target');
if (target === 'x') {
this.setState({ // 计算左侧box的宽度
leftWidth: this.state.leftWidth + (e.clientX - this.startX),
});
} else if (target === 'y') {
this.setState({ // 计算底部box的高度
bottomHeight: this.state.bottomHeight + (e.clientY - this.startY),
});
}
}
经过上面简单的三个步骤,我们就基本实现了一个支持可拖拽的流式布局。在上面主要用到了拖拽的三个事件:
ondragstart 获取鼠标的起始位置
ondragover 阻止默认事件
ondrop 获取鼠标的终点位置,重新计算div的宽高
3. 总结说明
官方文档称 Internet Explorer 9、Firefox、Opera 12、Chrome 以及 Safari 5 均支持拖放。
注释:在 Safari 5.1.2 中不支持拖放。但在各个浏览器上仍然有不同的表现:
如何获取鼠标位置
在拖拽过程中,最重要的就是获取被拖对象、被拖对象的起始位置和放置位置,这些属性都可以通过 ondragstart 和 ondrop 来获取;而在某些业务场景中,我们是需要获取拖拽过程中鼠标的位置;但在Firefox中,ondrag、ondragover 的event对象中的clientX、clientY、offsetX、offsetY等值都是0,这个时候我们是可以通过监听document对象的dragover事件来实时获取鼠标所在的位置,代码示例如下:
document.addEventListener(
'dragover',
(e) => {
e = e || window.event;
const dragX = e.pageX;
const dragY = e.pageY;
console.log("X:" + dragX +"Y:" + dragY)
},
false
)
拖拽元素为img对象
当你的拖放元素为img对象或者包含img对象时,在Firefox中总是会在新窗口中打开图片链接,而其他浏览器不会。解决办法是在ondrop事件中添加阻止事件冒泡的行为 event.stopPropagation()。