基於JQuery的拖動插件有幾個都相當好用,效果也很好,但再好,還是自己琢磨一個最好。所以,我的理念就是即使實際項目中使用別人的程序,自己也得根據理解和想法寫一個出來。僅當學習使用
測試
ID | 拖動 | 名稱 |
1 |
拖
|
這里是第一行 |
2 |
拖
|
這里是第二行 |
3 |
拖
|
這里是第三行 |
4 |
拖
|
這里是第四行 |
5 |
拖
|
這里是第五行 |
要求
拖動排序,從名字就不難想像,就是按住一行數據拖到想要的排序位置,保存新的排序隊列。
思路
首先給列表行建立錨點,綁定mousedown和mouseup事件,當鼠標移動到想要插入的位置時,將對象行移動到目標行,然后對其經過的所有行進行排序處理。
思路很簡單,但這里面仍然有幾個問題要注意
1、移動到什么位置可以視作要插入到目標行的位置。
2、移動出了頂端和底端時,判斷為第一和最后。
3、向上移動和向下移動的處理
解決
關於事件
Javascript里鼠標按下和放開事件為onmousedown,onmouseup,JQuery里是mousedown,mouseup,所以,這里使用mousedown和mouseup
首先,要知道界面有多少行,每一行有多高,因為要判斷鼠標的移動距離
var tbodyHeight=setting.frame.outerHeight(); //setting.frame,父對象 var lineNum=$("."+setting.dgLine).length; //setting.dgLine,每一行的classname var lineHeight=Math.ceil(tbodyHeight/lineNum);
之所有要取lineNum(行數),除了計算行高外,還有個目的是要使用index(),通過序列索引值來進行移動行到目標位置
當mousedown事件觸發后,就要開始計算鼠標移動的距離,用於判斷該行到底要移動到什么位置
dgid=$(this).attr(setting.id); //移動行的ID,setting.id,是每一行用來標記ID的名稱 thisIndex=$("#"+setting.linePre+dgid).index(); //該行的索引,setting.linePre,每一行ID關輟 thisLineTop=$("#"+setting.linePre+dgid).offset().top; //該行的top值 topDistance=thisIndex*lineHeight; //該行距離第一行頂端的距離 downDistance=(lineNum-thisIndex-1)*lineHeight; //該行距離最后一行底端的距離
dgid主要是用來區分每一行的標識,一般的列表都是程序循環輸出來的,如果沒有這樣一個ID,就分不出哪行是哪行,所以,在HTML上,需要定義一個存放ID的家伙。程序通過attr就是來取這個值,保證每一行都有自己唯一的值。
thisLineTop,主要是用來和鼠標移動位置進行高度計算,然后根據行高、索引值來判斷是移動到哪一行了。還有個作用是用來確定是否按在了移動錨點 上,如果有值,說明是,那后面的mouseup就是成立的,如果沒有值,說明沒有按到錨點上,mouseup不執行任何操作。為什么要這樣做呢?因為不管 在頁面的什么位置點鼠標,都會觸發mouseup事件,如果沒有一個判斷,就會不停的執行,那就會產生一些問題。
topDistance和downDistance,用來判斷鼠標有沒有移出列表的范圍,如果移除了,也就是鼠標移動的距離大於topDistance或downDistance,那就可以判斷為需要移動到第一行或最后一行。
mousedown事件主要做的,就是這幾件事情,當然,為了效果,還可以增加一些東西
$("#"+setting.linePre+dgid).css('background',setting.lineHighlight); //高亮移動行 var left=e.pageX+20; var top=e.pageY; dg_tips(left,top); //創建一個提示層 $('body').css('cursor','move'); //更改頁面的鼠標手勢 $("body").disableSelection(); //禁止按下鼠標后移動鼠標時選中頁面元素 setting.frame.mousemove(function(e){ //讓提示層跟隨鼠標移動 $("#dgf").css({"left":e.pageX+setting.tipsOffsetLeft+'px',"top":e.pageY+'px'});
這些的目的,只是讓操作起來更有效果,比如高亮行,就是要讓用戶知道,他們操作的是哪一行。提示層的作用也一樣。
關於禁止選中,.disableSelection();這是jQuery_UI里帶的方法,如果你有使用jQuery_UI,那可以直接使用它,如果沒有使用,可以這樣做
$('body').each(function() { $(this).attr('unselectable', 'on').css({ '-moz-user-select':'none', '-webkit-user-select':'none', 'user-select':'none' }).each(function() { this.onselectstart = function() { return false; }; }); });
取消禁止選擇
$('body').each(function() { $(this).attr('unselectable', '').css({ '-moz-user-select':'', '-webkit-user-select':'', 'user-select':'' }); });
考慮到通用性,所以后面的代碼里,不會使用.disableSelection();
好了,下面是mouseup事件。這里mouseup事件是綁定在body上的,因為mouseup如果只是綁定在錨點上,那當鼠標移出錨點的時候,再松 開鼠標,會發現,這個mouseup事件不執行了,因為它會認為是別的對象的mouseup。所以,最保險的方法是 用$('body').mouseup。這樣基本上就不會有問題。
mouseup觸發后,首先就要判斷thisLineTop是不是有值,防止無意義的事件執行。跟着判斷鼠標移動的距離是正還是負,也就是向上移動還是向下移動。
var moveDistance=e.pageY-thisLineTop;
根據不同的方向作不同的處理
if(moveDistance<0){ if(thisIndex!=0){ moveDistance=Math.abs(moveDistance); //為負數的時候,取一下絕對值 if(moveDistance>lineHeight/2){ //判斷移動距離是否超過行高的1/2 if(moveDistance>topDistance){ //如果移動距離大於該行到頂邊的距離 focusIndex=0; }else{ focusIndex=thisIndex-Math.ceil(moveDistance/lineHeight); } $("."+setting.dgLine).eq(focusIndex).before($("#"+setting.linePre+dgid));//將該行插入目標位置 } } }else{ if(thisIndex!=lineNum-1){ if(moveDistance>lineHeight/2+lineHeight){ if(moveDistance>downDistance){ focusIndex=lineNum-1; }else{ focusIndex=thisIndex+Math.ceil(moveDistance/lineHeight)-1; } $("."+setting.dgLine).eq(focusIndex).after($("#"+setting.linePre+dgid)); } } }
之所以判斷移動距離是否超過行高的1/2,是因為如果只移動一小點,可以視作不移動。在計算目標索引值的時候,使用了Math.ceil,最進位,而當移動距離大於0的時候,取了進位還要-1,因為是向下嘛。
向上移動和向下移動使用了不同的插入方法,before和after,可以試着想一下為什么要使用before和after。
移動完了,還得把按下鼠標時使用的效果給去除掉
$("#dgf").remove();//移除提示層 $("#"+setting.linePre+dgid).css('background','');//將高亮的行變為普通 dgid='';//將移動行的ID值空 thisLineTop=0;//將移動行的Top值至0 $('body').css('cursor','default');//更改鼠標手勢為默認
基本上的情況就是這樣,主要問題就是在處理移動和判斷在哪里插入的問題上。其它的都非常簡單。
下面給出完整的封裝程序,包括更新數據部分

/* * * DragList.js * @author fuweiyi <fuwy@foxmail.com> * */ (function($){ $.fn.DragList=function(setting){ var _setting = { frame : $(this), dgLine : 'DLL', dgButton : 'DLB', id : 'action-id', linePre : 'list_', lineHighlight : '#ffffcc', tipsOpacity : 80, tipsOffsetLeft : 20, tipsOffsetTop : 0, JSONUrl : '', JSONData : {}, maskLoaddingIcon : '', maskBackgroundColor : '#999', maskOpacity : 30, maskColor : '#000', maskLoadIcon:'', }; var setting = $.extend(_setting,setting); var dgid='',thisIndex,thisLineTop=0,topDistance,downDistance; var tbodyHeight=setting.frame.outerHeight(); var lineNum=$("."+setting.dgLine).length; var lineHeight=Math.ceil(tbodyHeight/lineNum); $("."+setting.dgButton).mousedown(function(e){ dgid=$(this).attr(setting.id); thisIndex=$("#"+setting.linePre+dgid).index(); var left=e.pageX+20; var top=e.pageY; thisLineTop=$("#"+setting.linePre+dgid).offset().top; topDistance=thisIndex*lineHeight; downDistance=(lineNum-thisIndex-1)*lineHeight; $("#"+setting.linePre+dgid).css('background',setting.lineHighlight); dg_tips(left,top); $('body').css('cursor','move'); unselect(); setting.frame.mousemove(function(e){ $("#dgf").css({"left":e.pageX+setting.tipsOffsetLeft+'px',"top":e.pageY+'px'}); }); }); $('body').mouseup(function(e){ if(thisLineTop>0){ var moveDistance=e.pageY-thisLineTop; if(moveDistance<0){ if(thisIndex!=0){ moveDistance=Math.abs(moveDistance); if(moveDistance>lineHeight/2){ if(moveDistance>topDistance){ focusIndex=0; }else{ focusIndex=thisIndex-Math.ceil(moveDistance/lineHeight); } $("."+setting.dgLine).eq(focusIndex).before($("#"+setting.linePre+dgid)); dg_update(thisIndex,focusIndex); } } }else{ if(thisIndex!=lineNum-1){ if(moveDistance>lineHeight/2+lineHeight){ if(moveDistance>downDistance){ focusIndex=lineNum-1; }else{ focusIndex=thisIndex+Math.ceil(moveDistance/lineHeight)-1; } $("."+setting.dgLine).eq(focusIndex).after($("#"+setting.linePre+dgid)); dg_update(thisIndex,focusIndex); } } } $("#dgf").remove(); $("#"+setting.linePre+dgid).css('background',''); dgid=''; thisLineTop=0; $('body').css('cursor','default'); onselect(); } }); function dg_update(thisIndex,focusIndex){ dg_mask(); var start=thisIndex<focusIndex?thisIndex:focusIndex; var end=thisIndex<focusIndex?focusIndex:thisIndex; var ids='',vals=''; for(var i=start;i<=end;i++){ ids+=i==start?$("."+setting.dgLine).eq(i).attr(setting.id):','+$("."+setting.dgLine).eq(i).attr(setting.id); vals+=i==start?i:','+i; } $.getJSON(setting.JSONUrl,{'do':'changeorders','ids':ids,'vals':vals},function(d){ $("#dg_mask").remove(); }); } function dg_mask(){ var W=setting.frame.outerWidth(); var H=setting.frame.outerHeight(); var top=setting.frame.offset().top; var left=setting.frame.offset().left; var mask="<div id='dg_mask'><img src='"+setting.maskLoadIcon+"' alt='' /> 正在使勁的保存...</div>"; $('body').append(mask); $("#dg_mask").css({"background":"#999","position":'absolute','width':W+'px','height':H+'px','line-height':H+'px','top':top+'px','left':left+'px','filter':'alpha(opacity='+setting.maskOpacity+')','moz-opacity':setting.maskOpacity/100,'opacity':setting.maskOpacity/100,'text-align':'center','color':'#000'}); } function dg_tips(left,top){ var floatdiv="<div id='dgf' style='padding:5px 10px;border:1px solid #444;background-color:#fff;filter:alpha(opacity="+setting.tipsOpacity+");moz-opacity:"+setting.tipsOpacity/100+";opacity:"+setting.tipsOpacity/100+";position:absolute;left:"+left+"px;top:"+top+"px;'>移動一條記錄</div>"; $('body').append(floatdiv); } function unselect(){ $('body').each(function() { $(this).attr('unselectable', 'on').css({ '-moz-user-select':'none', '-webkit-user-select':'none', 'user-select':'none' }).each(function() { this.onselectstart = function() { return false; }; }); }); } function onselect(){ $('body').each(function() { $(this).attr('unselectable', '').css({ '-moz-user-select':'', '-webkit-user-select':'', 'user-select':'' }); }); } } })(jQuery);
使用
<table cellpadding="5" cellspacing="0" class="listtable" id="listtable"> <thead> <tr> <td class="td50">拖動</td> <td class="td220">名稱</td> </tr> </thead> <tbody id="drag_table"> <!--{loop $lists $k $list}--> <tr id="list_{$list['id']}" action-id="{$list['id']}" class="drag_line"> <td><div class="drag_orders" action-id="{$list['id']}"><img src="{SYS_URL}views/admin/images/move.png" alt="" /></div></div></td> <td>這里是一行</td> </tr> <!--{/loop}--> </tbody> </table> <script type="text/javascript"> $("#drag_table").DragList({ dgLine:'drag_line', dgButton:'drag_orders', id:'action-id', linePre:'list_', JSONUrl:'{_ADMIN}?controller=contents&method=focus_form', maskLoadIcon:'{SYS_URL}views/admin/images/loading.gif' }); </script>
參數主要是dgLine,dgButton,id,linePre和JSONUrl,通過看HTML代碼,應該不難理解。
關於拖動排序就是這么多了,當然還可以把效果做得更漂亮些,提示更清楚點,有助於用戶體驗