在前端web页面中,为了提高用户体验,通常会希望将页面中的元素设计成可dragdop的,简化用户操作。这一设计特性在缺少鼠标的触摸屏设备上,显得更为重要。
在早期的应用中,我们通常需要借助第三方的javascript库(Jquery插件等)。在HTML5已经将这一特性引入,提供原生的支持,不用再借助第三方的javascript库。现代化的程序设计中,各种前端库的引入(Jquery, nodejs, ract, angularjs),提高了开发效率,而各自的库也有相应的插件来处理dragdrop,使得开发更为便捷。那HTML5原生的支持,到底是怎么样的?
关键就在DOM元素的 draggable 属性,如果是 draggable = “true” ,表示该元素可被拖拽。 draggable = “false” 或其它值则不可拖拽。
看一个例子:
本样例以table形式呈现,本质是div+css布局(从业务的角度,每一个是一个业务单位,而非单独的列。但只有test item列中的元素是可拖拽的)。每一行作为一个业务单元,但只有test item所在列的元素可拖拽。当其被拖拽到其它其它行的时候,以行为单位进行整体交换。
看一下行的元素布局
1 <div id="div0" class="containerDiv" ondrop="drop(event)" data-dropable="true" ondragover="dragOver(event)" data-dropEl=‘drag0‘ > 2 <span class="headerCellStyle"> 1</span> 3 <span id="drag0" ondragstart="drag(event)" data-dragable="true" data-dropElHost=‘div0‘ data-sequence=‘1361‘ data-key=‘1361‘ data-orginalSequence=‘1361‘> 4 <span style="width:4.3vw;" class="bodyCellStyle" >100</span> 5 <span style="width:8.9vw;" class="bodyCellStyle" >Rake</span> 6 <span id="testitem0" style="vertical-align:middle; display:inline-block;width:21vw;overflow:hidden;white-space:nowrap" class=" dropElCSS" onmouseover="OnMouseEnterEvent(event)" onmouseout="OnMouseLeaveEvent(event)">Max_Load_Clamp </span> 7 <span style="display:inline-block; width:8.9vw;border-left:0.1vw solid #aaaaaa;border-right:0px;padding:0.2vw 0.2vw;white-space:nowrap" >2016-09-25 11:07:39</span> 8 </span> 9 </div>
可拖拽DOM元素 id="testitem0" 需要特别注意,以testitem作为前缀,在拖拽判断的时候会使用到:
1 if(targetContainer.id.indexOf("testitem")>=0) { 2 targetContainer = ev.target.parentNode || ev.target.parentElement; 3 targetContainer = targetContainer.parentNode || targetContainer.parentElement; 4 }
因为span的限制,drapdrop的属性会冒泡到第一个block元素上,影响到parent容器DIV,形成拖拽错乱:drag的元素被pending到drop的容器中,本应该是需要交换的两个元素被合并到一个DIV容器中。通过设定鼠标进入离开事件onmouseover="OnMouseEnterEvent(event)" onmouseout="OnMouseLeaveEvent(event)" ,设定属性dragdrop为true或者false.
1 function SetItemDragable(dragable, dragEl){ 2 var gloableStatus = true; 3 if(dragEl) 4 dragEl.setAttribute("draggable", gloableStatus&&dragable); 5 } 6 7 function OnMouseEnterEvent(ev){ 8 var elID = ev.target.id; 9 var parentEl = ev.target.parentNode || ev.target.parentElement; 10 if(parentEl) SetItemDragable(true, parentEl); 11 } 12 13 function OnMouseLeaveEvent(ev){ 14 var elID = ev.target.id; 15 var parentEl = ev.target.parentNode || ev.target.parentElement; 16 if(parentEl) SetItemDragable(false, parentEl); 17 }
MouseEvent
你会注意到在dragdrop元素的parent元素上,会有 data- 开头的这种自定义属性:
data-dragable="true" data-dropElHost=‘div0‘ data-sequence=‘1361‘ data-key=‘1361‘ data-orginalSequence=‘1361‘
这些属性是被用来存储业务数据或其它自定义用途的数据。其中,最重要的data-dropElHost指向行级元素的DOM(拖拽最终的效果中的整体元素)。 data-dragable="true" 标记该元素从业务角度是否可拖拽,有别于HTML5的自定义属性 dragdrop = "true/false" ,它们唯一的区别就是业务上和技术角度是否可拖拽。
页面上的Save按钮,点击后显示拖拽后变动的元素,将 data-key, data-sequence 中的值作为一个二元组(data-key, data-sequence)表示一个元素的最终值,以字符‘;’作为多个元素值的分隔符。
完整的javascript代码为:
1 function dragOver(ev) { 2 ev.preventDefault(); 3 4 } 5 6 function drag(ev) { 7 if(ev.target.getAttribute("data-dragable")!="true") return; 8 ev.dataTransfer.setData("text", ev.target.id); 9 } 10 11 function drop(ev) { 12 ev.preventDefault(); 13 var targetContainer = ev.target; 14 15 if(targetContainer.id.indexOf("testitem")>=0) { 16 targetContainer = ev.target.parentNode || ev.target.parentElement; 17 targetContainer = targetContainer.parentNode || targetContainer.parentElement; 18 } 19 20 if(targetContainer.getAttribute("data-dropable")!="true") return; 21 22 var data = ev.dataTransfer.getData("text"); 23 var dropEl = document.getElementById(data); 24 targetContainer.appendChild(dropEl); 25 26 var dropElHost = document.getElementById(dropEl.getAttribute("data-dropElHost")); 27 var hostOrginalEl = document.getElementById(targetContainer.getAttribute("data-dropEl")); 28 targetContainer.removeChild(hostOrginalEl); 29 dropElHost.appendChild(hostOrginalEl); 30 31 //swap host id of each element 32 hostOrginalEl.setAttribute("data-dropElHost", dropEl.getAttribute("data-dropElHost")); 33 dropEl.setAttribute("data-dropElHost", targetContainer.id); 34 35 //swap drop element id of its container div 36 targetContainer.setAttribute("data-dropEl", data); 37 dropElHost.setAttribute("data-dropEl", hostOrginalEl.id); 38 39 //swap sequence of drop element 40 var hostOrginalElSequence = hostOrginalEl.getAttribute("data-sequence"); 41 hostOrginalEl.setAttribute("data-sequence", dropEl.getAttribute("data-sequence")); 42 dropEl.setAttribute("data-sequence", hostOrginalElSequence); 43 44 45 SetSaveBtnStatus(true); 46 47 } 48 49 function ShowSequence() { 50 var idPrefix = "drag"; 51 var result = ""; 52 53 var data=""; 54 var dragElementLength = 504; //there‘re not so many elements now. 55 for (var i = 0; i < dragElementLength; i++) { 56 var id = idPrefix + i; 57 var el = document.getElementById(id); 58 if(el==null) continue; 59 var currentSequence = el.getAttribute("data-sequence"); 60 var orginalSequence = el.getAttribute("data-orginalSequence"); 61 62 if(currentSequence == orginalSequence) continue; 63 64 data += "("+el.getAttribute("data-key") +"," +currentSequence+")"; 65 } 66 67 if(data.length==0){ 68 MessageBox("ERROR: There‘s no item for order change!"); 69 return; 70 } 71 72 document.getElementById("resultInput").value = escape( data); 73 74 //document.getElementById("form1").submit(); 75 document.getElementById("resultDiv").innerHTML = "The updated items are(data-key, data-sequence):" + data; 76 } 77 78 function SetSaveBtnStatus(activity){ 79 document.getElementById("saveBtn").disabled = !activity; 80 } 81 82 function SetItemDragable(dragable, dragEl){ 83 var gloableStatus = true; 84 if(dragEl) 85 dragEl.setAttribute("draggable", gloableStatus&&dragable); 86 } 87 88 function OnMouseEnterEvent(ev){ 89 var elID = ev.target.id; 90 var parentEl = ev.target.parentNode || ev.target.parentElement; 91 if(parentEl) SetItemDragable(true, parentEl); 92 } 93 94 function OnMouseLeaveEvent(ev){ 95 var elID = ev.target.id; 96 var parentEl = ev.target.parentNode || ev.target.parentElement; 97 if(parentEl) SetItemDragable(false, parentEl); 98 }
JavaScript
样式表为:
1 .containerDiv {min-width:350px;width:auto;border:0.1vw solid #aaaaaa;float:none;border-top:0px;} 2 .dateTip{font-style:italic;} 3 .userManual{min-width:350px;width:auto;/*border:0.1vw solid #aaaaaa;*/} 4 .headerCellStyle{font-weight:bold;width:3vw;display:inline-block;margin-left:0.4vw;padding:0.2vw 0px;border-right:0.1vw solid #aaaaaa;white-space:nowrap;} 5 .bodyCellStyle{display:inline-block;margin-left:0.4vw;padding:0.2vw 0px;border-right:0.1vw solid #aaaaaa;white-space:nowrap;} 6 .dropElCSS { width:90%; cursor: pointer;border-radius: 1vw;background-color:white; box-shadow: 0.1vw 0.3vw 0.3px #888888;border:0px dashed black ; }
CSS
附上完整的DEMO