chaojidan

导航

拖动插件的一些常见问题

最近项目需要,我要实现一个拖动的功能。大概的意思是:你邮箱里面的邮件列表,你可以通过鼠标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请求的情况。

以上只是我简单的看法,大家如果有更好的意见,请评论,我们探讨下。

 

 

 

 

加油!

posted on 2015-03-10 22:31  chaojidan  阅读(1719)  评论(0编辑  收藏  举报