HTML5拖放详解

1,695 阅读4分钟

1. 拖放的基本概念

拖(drag)放(drop)是 HTML5 标准的组成部分,拖放是一种常见的特性,即抓取对象以后拖到另一个位置,任何元素都可以被拖放。 **

2. 拖放的几个事件

下面举一个简单的栗子来详细说明拖放的常用事件及其应用: 20200522150333.jpg 需求说明:如上图,这是一个常见的三栏布局,要求图中箭头部分可上下、左右拖动,使得三个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()。