可拖动DIV层的实现方法

可拖动DIV层的实现方法

这几天做了一个英文单词搜索的谷歌扩展,其中的划词搜索功能会产生一个可托拖动的DIV层来作为结果显示,为了做一个较为完善的拖动层,花费了很长时间进行设计与调试。在此把心得总结了一下,讲讲实现方法与关键点。

先来看看效果:在线实例DEMO

基本思路

  1. 有一个DIV层,设定position属性为absolutefixed,通过更改其lefttop来更改层的相对位置。
  2. 在DIV层上绑定mousedown事件,设置一个拖动开始的标志为true,比如本例的isMouseDown,作为拖动的开始。
  3. document上绑定mousemove事件,移动鼠标时,根据拖动开始标志的真假,动态给DIV层设定根据当前鼠标位置的left,top值,实现按下鼠标后进行的拖动。
  4. 在DIV层上绑定mouseup事件,设置拖动开始标志为false,结束拖动。

写成代码,大概就是这个样子:

CSS

body,html{
  height:100%;
  width: 100%;
}


#draggable {
  background: #eee;
  display:flex;
  align-items: center;
  justify-content:center;
  position: fixed;
  left:0;top: 0;
  width: 200px;
  height:200px;
  font-size:2rem;
}

HTML

<div id="draggable">drag mediv>

Js

var isMouseDown;

draggable.addEventListener('mousedown', function() {
  isMouseDown = true;
})

document.addEventListener('mousemove', function(e) {
  if (isMouseDown) {
    draggable.style.left = e.clientX  + 'px';
    draggable.style.top = e.clientY + 'px';
  }
})

draggable.addEventListener('mouseup', function() {
  isMouseDown = false;
})

通过这样的设定,可以得到最基本的一个拖放DIV:

在线实例1:simple draggable div#1

操作体验一下,再来深入研究细节内容(接下来的实例相关代码不再重复,只增加不同内容)

进阶细节

拖动时鼠标于DIV的相对位置

实例1中最明显的问题就是,不论在何处点击DIV,拖动时DIV都会飞到以鼠标为原点的位置。这是因为单击鼠标时,鼠标在有一个相对于DIV的坐标位置,在紧接着的拖动操作中没有计算这个相对距离,修改代码如下:

draggable.addEventListener('mousedown', function(e) {
  isMouseDown = true;
  initX = e.offsetX;
  initY = e.offsetY;
})

document.addEventListener('mousemove', function(e) {
  if (isMouseDown) {
    var cx=e.clientX,cy=e.clientY;
    draggable.style.left = e.clientX - initX  + 'px';
    draggable.style.top = e.clientY - initY + 'px';
  }
})

在线实例2:simple draggable div#2

拖动时选中文字

实例2实现了在哪点击,在哪拖动的效果。但是如果拖动速度过快,就会出现文字被选中的情况,如果页面除了拖动层还有其他文字内容,那么就会出现只要拖动DIV,页面其余内容就会被选中的问题,此时需要增加一个CSS属性user-select:none,这个属性是防止元素被选中:

CSS:

.no-select {
  user-select: none;
}

HTML:

<div id="draggable" class="no-select">drag mediv>

同时,在开始拖动时,也要给body增加no-select的样式,并在拖动结束时,移除这个样式:

draggable.addEventListener('mousedown', function(e) {
  isMouseDown = true;
  document.body.classList.add('no-select');
  initX = e.offsetX;
  initY = e.offsetY;
})

draggable.addEventListener('mouseup', function() {
  isMouseDown = false;
  document.body.classList.remove('no-select');
})

这样就可以防止拖动DIV时选中其余的文字内容。

DIV被拖动到边界处,消失了,限定移动范围?

实例中仅有一个可拖动的方块演示起来倒不是很能说明问题。我在做扩展时,由于需要展示内容,拖动层设定为只有顶部可以拖动,当我拖动顶部到网页顶部时,问题出现了,我无法再拖动浮动层,只能关闭重新开启,而且当我启用固定在屏幕上功能时,拖动出边界后变无法关闭浮动层,只能刷新网页,于是我意识到拖动需要有范围限制。

逻辑很简单,当鼠标移动到边界时(页面内外部交界),会产生小于0或者大于网页宽度或高度的坐标,只要加入判断,鼠标超过边界时,手动规定窗口的lefttop 值,比如拖动时,当鼠标从页面最上端移出,鼠标Y坐标小于0时,就规定浮动层的top值为0。

其中有个很隐秘的经验,如果在documentmousemove事件上输出鼠标移动的坐标,当鼠标移动出浏览器是没有任何坐标信息的,但是如果是按下鼠标左键再去移动,移动出浏览器不松开鼠标的情况下,就可以看到输出的负值或大于网页宽高的鼠标坐标信息。这个坐标信息,是限制DIV拖动范围的关键。

计算临界情况的数学关系之后,需要用到DIV的宽高,在mousemove事件中增加判断,代码如下:

var height = draggable.offsetHeight,
    width = draggable.offsetWidth;

document.addEventListener('mousemove', function(e) {
  if (isMouseDown) {
    var cx = e.clientX - initX,
        cy = e.clientY - initY;
    if (cx < 0) {
      cx = 0;
    }
    if (cy < 0) {
      cy = 0;
    }
    if (window.innerWidth - e.clientX + mouseX < width) {
      cx = window.innerWidth - width;
    }
    if (e.clientY > window.innerHeight - height+ mouseY) {
      cy = window.innerHeight - height;
    }
    draggable.style.left = cx + 'px';
    draggable.style.top = cy + 'px';
  }
})

在线实例3:simple draggable div#3

拖动层失去焦点

在实例3的基础上尽情拖动,很快就会发现,如果按下鼠标时,移动出网页,虽然浮动层很好的被限定在了页面内,但是会出现松开鼠标,再次回到页面,浮动层会跟随鼠标移动的情况,相当于在页面外松开鼠标无效,没有结束拖动。

这个跟上面的情况有相似的地方,因为结束拖放的mouseup事件是绑定在DIV上,这就导致了该事件无法判断出鼠标是否移动到了页面外,根据上面的“隐秘的经验”,需要在document上也增加一个mouseup事件的绑定,来专门对付鼠标移出页面外时松开鼠标左键的情况:

document.addEventListener('mouseup', function(e) {
  if(e.clientY > window.innerWidth || e.clientY<0 || e.clientX<0 ||e.clientX>window.innerHeight){
    isMouseDown = false;
    document.body.classList.remove('no-select');
  }
});

终极难题:拖动层上放iframe

我最初卡壳的原因就是,查询完单词的结果显示界面是一个内嵌在拖动层上的iframe界面,我拖动DIV移动时,如果鼠标速度过快,移动到了iframe上,这时的鼠标事件就跑到了iframe中,原本的mousemove事件都监听不到鼠标了。

还有其他种种BUG都是由于iframe导致的,我尝试了很多关于iframe的方法,都无法实现。

最终根据给页面增加无法选择文本的CSS样式,依葫芦画瓢,只需给iframe层增加一个pointer-events:none的CSS样式。该样式是透明当前元素上的鼠标事件,也就是说,在元素上的鼠标事件相当于直接作用于下层的元素,该元素只有视觉上的展示,没有鼠标事件。

draggable.addEventListener('mousedown', function(e) {
    content.classList.add('pointer-events');
}

draggable.addEventListener('mouseup', function(e) {
    content.classList.remove('pointer-events');
}                                                   

最后,所有这些内容汇总在一起,正是开篇的DEMO。

结束

效率有提升的空间,等我以后有机会学会优化了,再更新。

你可能感兴趣的:(JS/CSS/HTML,前端自习室)