最近项目需要,我要实现一个拖动的功能。大概的意思是:你邮箱里面的邮件列表,你可以通过鼠标mousedown后,通过鼠标移动mousemove,把特定的邮件拖动到垃圾箱啊或者草稿箱啊,如果拖动到的地方不是像垃圾箱或草稿箱的元素,就不做处理。
这个功能在邮箱项目上是很普遍的,我稍微看了下我们公司的标准邮箱的实现方法,其中有一个疑问是:为什么不用节流的方式来控制mousemove的操作。大家都知道像mousemove和scroll这种事件,你拖动一下,就可能触发很多次,导致回调方法也执行很多次,性能不好,如果加上函数节流的这种机制,不是可以性能优化吗?这个问题,后面会有详细的解释。
下图就是需要实现的功能截图:
知道了项目的需求,我就开始写代码实现了。
第一点:你必须绑定邮件列表的mousedown事件。由于邮件数目比较多,你不可能给每一封邮件都绑定mousedown事件,这样会影响性能。因此,连初学者都知道要绑定邮件列表的父元素,这样你点击某一封邮件的时候,事件会冒泡到父元素,父元素就会收到这个事件,然后触发事件回调函数,在事件回调函数里面,通过event.target,我们就可以知道用户点击了那一封邮件,进而再处理。这就是所谓的事件委托机制。如果想对事件机制有更深的了解,请看:http://www.cnblogs.com/chaojidan/p/4167675.html。
因此,具体的实现细节如下:
$("div.mailList").on("mousedown",function(event){ .......}
给邮件列表的父元素div绑定mousedown事件,大家都知道jQuery绑定事件有很多种方式,但是我推荐使用on方法,理由有两点:1.有些绑定方法在内部其实就是调用on方法进行事件绑定的。2.on方法绑定事件,兼容性好,而且后面添加的元素也不会出问题(有些绑定方法,当元素是后面添加的时候,绑定就失效了)。如果需要更详细的了解,请自行百度。
第二点:绑定好了mousedown事件后,就要绑定mousemove事件,那这个事件绑定在哪里呢?大家可以想想看,你拖动此邮件元素的时候,是不是在整个页面都应该触发mousemove事件呢,因此,我们需要这样绑定mousemove事件:
$(document).on("mousemove", function(event){ ........ }
然后,我们就在这个回调函数里面,通过event.target就可以得到鼠标拖动的地方的元素节点。我们通过判断这个元素节点,就可以知道邮件是否可以拖动到这个地方。
第三点:绑定完mousedown事件后,我们就要绑定mouseup事件了,此事件理所当然也是绑定在document上。
$(document).on("mouseup",function(event){ .......}
在此回调函数中,我们就可以通过上面mousemove的回调方法中的判断,是否进行ajax请求。如果鼠标拖动的地方可以接收邮件,那么就进行ajax请求,如果不行,就不用进行ajax请求。
然后,还需要在此回调函数中,取消事件的绑定:
$(document).off("mousemove"); $(document).off("mouseup");
至此,整个的框架就已经出来了。
$("div.mailList").on("mousedown",function(event){ .......
$(document).on("mousemove", function(event){ ........ }
$(document).on("mouseup",function(event){ ......
$(document).off("mousemove"); $(document).off("mouseup");
}
}
弄完这些之后,基本的功能实现了。
深入进去,你就会发现以下几个问题:
第一个问题:当你点击邮件元素,进行移动的时候,会让其他的文本元素变成蓝色的,这是浏览器的默认风格。但是通过,event.preventDefault()方式,只能解决chrome浏览器,但是火狐,IE下,还是不行。因此,百度得到以下方法:
$("body,html").css({ //解决鼠标拖动时,不会让其他元素变成蓝色 "-moz-user-select": "none", "-khtml-user-select": "none", "user-select": "none" });
当鼠标mousedown时,在回调函数中执行上面的代码。
当鼠标mouseup时,在回调函数中执行下面的代码:
$("body,html").css({ "-moz-user-select": "auto", "-khtml-user-select": "auto", "user-select": "auto" });
问题解决。
第二个问题:当我们拖动元素的时候,需要实时的显示一个div元素,这个div元素会提示我们当前鼠标的地点是否可以接受邮件。因此,我们需要创建一个div元素,由于此div是根据窗口定位的,因此我们只要设置它的position:fixed。
$("<div style=‘border: 1px solid;position:fixed;display:none‘>");
然后,把此元素添加到页面上去。(这里我之前用$(document).append()方法添加此元素,但是一直都添加不上,看jQuery源码,原来只有nodeType=1元素节点或=11文档碎片节点的时候,才能添加元素。而document的nodeType=9。)
$("body").append(divTip);
然后,我们在mousemove的回调函数中,把这个divTip显示出来。
$(divTip).css({ "left": x+20, "top": y+20, "z-index" : 99999, "display": "block" });
其中,x = event.clientX,y = event.clientY。
最后,在mouseup的回调函数中,把这个divTip隐藏。
$(divTip).css({ "display": "none" }); $(divTip).remove();
这里,我就要讲一下,如果我们在mousemove的回调函数中使用函数节流的话,那么,就会出现divTip不能实时的跟着鼠标的拖动,移动到鼠标的位置。其实这不是问题,真正的问题是,当你移动到可以接受邮件的元素时,divTip会显示可以接受,这时,你移动鼠标,不小心移到divTip上时,divTip就会显示不可以接受(divTip本身是不能接受邮件的),但过一下,divTip移动后,鼠标就会落在了可以接受邮件的元素上,这时divTip又显示了可以接受。由于你不是实时的,所以divTip就会显示一下不可接受,然后再变成可接受,闪烁的情况会出现。因此,没有用到函数节流。
最后一个问题:如果页面存在iframe的情况,你拖动元素,在iframe下拖动,或者释放鼠标按钮,那么你在document下绑定的mousemove和mouseup就会失效,导致问题出现。当然只有chrome浏览器下没有问题,其他浏览器下都失效了。那如何解决这个问题呢?
我的想法是,在页面上的iframe中绑定mouseup和mousemove事件,然后在mouseup的回调函数中,解绑mouseup和mousemove就行了。
for(var i= 0,len=window.frames.length;i<len;i++){ //其实这里有最简单的方法,就是直接取那个特定的iframe,不用循环去取 iframes[i]= window.frames[i]; $(iframes[i].document).on("mouseup",function(event){ ........ }); }
这样绑定后,虽然解决了页面存在iframe时,document绑定mouseup和mousemove失败的问题,但是新的问题出现了,在iframe中你取到的
var x = event.clientX; var y = event.clientY;
是有问题的,因为iframe在你的页面中存在一定的位移,而此时的event.clientX是相对于iframe来算的,因此你需要加上iframe的位移
var iframeLoc = $("#ueditor_0").offset();
获取iframe元素,调用jQuery的offset方法,就可以搞定了。
然后,你判断,如果用户把邮件拖到iframe中时,你就加上这个iframe的位移:
$(divTip).css({ "left": x + (iframeLoc ? iframeLoc.left : 0), "top": y + (iframeLoc ? iframeLoc.top : 0), "z-index" : 99999, "display": "block" });
问题,就解决了。
但是,如果这时,用户拖动了滚动条,这时就会产生滚动的距离,这样上面的计算方法在iframe中就会出错了(这时的event.clientX需要减去滚动距离的scrollLeft)。因此,当在iframe中拖动邮件元素时,我们还需要绑定scroll事件,如果滚动触发,我们就需要减去滚动的位移。
$(document).on("scroll",function(){ scrollLeft = $(window).scrollLeft(); scrollTop = $(window).scrollTop(); isScroll = true; });
在mousemove时,判断是否在iframe中,如果在iframe中,并且isScroll为true,就必须减去滚动距离(这里,我们通过在iframe的位移中减去scrollLeft,跟在event.clientX减去scrollLeft是一样的效果)。
if(!iframeLoc || isScroll){ iframeLoc = $("#ueditor_0").offset(); iframeLoc.left = iframeLoc.left - scrollLeft; iframeLoc.top = iframeLoc.top - scrollTop; isScroll = false; }
最终,问题都得到了解决。
当然,上面的拖动插件,我还没有加入ajax请求,也许加入后,会出现更多的问题。这里,我们不讨论ajax请求的情况。
以上只是我简单的看法,大家如果有更好的意见,请评论,我们探讨下。
加油!