自定義模塊展示風格
阿新 • • 發佈:2017-12-03
阻止默認行為 dom attr opacity etl truct -i aar -s
demo:https://hanjiafushi.com/personal/DragTool/DragTool.html
源文件:https://pan.baidu.com/s/1o8usM0q
嗅探技術:
User Agent中文名為用戶代理,簡稱 UA,它是一個特殊字符串頭,使得服務器能夠識別客戶使用的操作系統及版本、CPU 類型、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語言、瀏覽器插件等,
標準格式:瀏覽器標識 (操作系統標識; 加密等級標識; 瀏覽器語言) 渲染引擎標識 版本信息
各大瀏覽器User Agent:
Chrome:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36 Firefox:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 IE11:Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko IE10:Mozilla/5.0(compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0) Safari:Mozilla/5.0 (Windows; U; Windows NT 5.2) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13 360兼容模式:Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko 360極速模式:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
js可以訪問瀏覽器的信息:
其中appVersion以下的屬性都不能夠返回針對瀏覽器的實質性信息(除了userAgent),而appCodeName(瀏覽器代碼名) 在所有以 Netscape 代碼為基礎的瀏覽器中,它的值是 "Mozilla"。為了兼容起見,在 Microsoft 的瀏覽器中,它的值也是 "Mozilla"。而
appMinorVersion在很多瀏覽器上都沒有定義(如火狐 Chrome),而appName 屬性是一個只讀到字符串,聲明了瀏覽器的名稱。在基於 Netscape 的瀏覽器中,這個屬性的值是 "Netscape",appVersion返回的信息大體和userAgent一樣。使用嗅探技術來檢測瀏覽器不是完全之策,比如
某瀏覽器宣稱自己百分百支持現有的所有新特性,然後可能結果並不如人意,或者說例如360瀏覽器,使用嗅探技術並不能很好的檢測出來,最好的方法還是使用特性檢測,在使用前判斷是否支持這一屬性,如果支持則繼續執行,如果不支持 則做好異常處理。
一個很好的H5特性檢測庫:Modernizr
自定義事件:
事件:事情,行為,比如說呼吸就是一次事件
綁定事件:即當事件發生時可以觸發我們綁定的方法
事件流:
自定義事件:
window.EventTarget = function(){ this.handlers = {}//事件數組 } EventTarget.prototype = { constructor:EventTarget,//將構造函數指向自身,因為給prototype設置對象會改變prototype的指向,這個時候利用constructor來判斷對象類型就會有問題,所以將constructor修改回來 addHandler:function(type,handler){//綁定自定義事件 if(typeof this.handlers[type] == "undefined"){ this.handlers[type] = [] } this.handlers[type].push(handler) }, fire:function(event){//觸發自定義事件 if(!event.target){ event.target = this; } if(this.handlers[event.type] instanceof Array){//指定觸發的事件類型,存在事件 var handlers = this.handlers[event.type]; var len = handlers.length; for(var i = 0; i < len; i++){//循環觸發事件 handlers[i](event); } } }, removeHandler:function(type,handler){//移除自定義事件 if(this.handlers[event.type] instanceof Array){ var handlers = this.handlers[event.type]; var len = handlers.length; for(var i = 0; i < len; i++){ if(handlers[i] == handler){//找到指定事件所處的索引 break; } } handlers.splice(i,1);//根據索引刪除指定事件 } } }
DomUnit.js:
裏面主要是將所有關於Dom的封裝操作起來,與當前程序的設計邏輯耦合度較高
window.DomUnit = { cssText:function(ele,cssAttr){ var cssAttrList = cssAttr; var cssText = ""; var cssList = ele.style.cssText.split(‘;‘); var len = cssList.length; for(var i = 0; i < len; i++){ var item = cssList[i]; if(!!item == false){continue;} var cssDic = item.split(‘:‘); var key = cssDic[0].trim(); var value = cssDic[1].trim(); if(!!cssAttrList[key] == false){ cssAttrList[key] = value; } } for(var key in cssAttrList) { cssText = cssText + key + ":" + cssAttrList[key] + ";"; } ele.style.cssText = cssText; }, getNextNode:function(target,abandonID){ var prevNode = null; var previousSibling = target.nextSibling; if(!!previousSibling){ if(previousSibling.nodeType != 1){ prevNode = this.getNextNode(previousSibling,abandonID); }else{ if(!!abandonID && previousSibling.getAttribute("id") == abandonID){ prevNode = this.getNextNode(previousSibling,abandonID); }else{ prevNode = previousSibling; } } } return prevNode; }, cloneCssAttr:function(ele,targetEle,cssList){ var cssAttr = window.getComputedStyle(ele,null); var borderWidth = parseInt(cssAttr["borderBottomWidth"]); var height = parseInt(cssAttr["height"]); var cssText = "", len = cssList.length, item = null; for(var i = 0; i < len; i++){ item = cssList[i]; if(item.useCssAttr){//直接使用cssList裏面的值 cssText = cssText + item.cssName + ":" + item.cssAttr + ";"; }else{ if(window.Browser.browser == "IE" && item.cssName == "height" && cssAttr["box-sizing"] == "border-box" && borderWidth > 0){ cssText = cssText + "height:"+(height+borderWidth*2)+"px;"; }else{ cssText = cssText + item.cssName + ":" + cssAttr[item.cssAttr] + ";"; } } } targetEle.style.cssText = cssText; }, isParent:function(parent,child){ if(child.parentNode == parent){ return true; }else if(child.parentNode == document.body || !child.parentNode){ return false; }else{ return this.isParent(parent,child.parentNode) } }, setEleBeforeTarget:function(ele,target,delEle,delSort){//delSort 0先插入後刪除, 1先刪除後插入 try{ var parentNode = target.parentNode; if(!!parentNode == false){return;} if(!!delEle){ if(!!delSort){ !!delEle.parentNode ? delEle.parentNode.removeChild(delEle) : "";//先刪除之前的節點 parentNode.insertBefore(ele,target);//添加到指定元素之前 }else{ parentNode.insertBefore(ele,target);//添加到指定元素之前 !!delEle.parentNode ? delEle.parentNode.removeChild(delEle) : "";//先刪除之前的節點 } }else{ parentNode.insertBefore(ele,target);//添加到指定元素之前 } }catch(e){ //重新獲取 console.log("dom操作引起的異常"); } }, resetDomFromLocol:function(dragArea){ var dragAreaDom = document.getElementById(dragArea); if(!dragAreaDom){console.log("DOM不存在");return;} var sortdom = LocalData.getValue(dragArea); if(!sortdom){ return; } var domTree = JSON.parse(sortdom); var len = domTree.length; for(var i = 0; i < len; i++){ var item = domTree[i]; var node = document.getElementById(item.id); node.style.width = item.width; dragAreaDom.removeChild(node); dragAreaDom.appendChild(node); } }, saveDomToLocal:function(dragArea,dragItem){ var dragAreaDom = document.getElementById(dragArea); var nodes = dragAreaDom.getElementsByClassName(dragItem); if(nodes.length == 0){return;} var len = nodes.length, parentNode = nodes[0].parentNode, item = null, domNode = {}, borderWidth = 0, cssAttr = null, width = 0, documentWid = parentNode.clientWidth, domTree = []; for(var i = 0; i < len; i++){ item = nodes[i]; domNode = {}; domNode["id"] = item.getAttribute("id"); cssAttr = window.getComputedStyle(item,null); width = parseInt(cssAttr["width"]);//獲取最終寬度 borderWidth = parseInt(cssAttr["borderBottomWidth"]);//獲取最終邊框寬度 if(window.Browser.browser == "IE" && cssAttr["box-sizing"] == "border-box" && borderWidth > 0){//因為IE默認在border-box模式下 不把邊框寬度算入width width = width + borderWidth*2; } domNode["width"] = width/documentWid*100 + "%"; domTree.push(domNode) } var domTreeStr = JSON.stringify(domTree); LocalData.setValue(dragArea,domTreeStr); } }
Rect.js:
封裝了關於匹配矩形重合度的操作,主要是判斷當前拖拽矩形與頁面剩余矩形的面積重疊區域是否大於兩個矩形中最小矩形的3分之2,提前將矩形的大小位置緩存起來,避免循環的時候多次請求DOM:
function Rect(OperaArea){ this.OperaArea = OperaArea;//操作區域 this.rectDataList = [];//保存所有矩形數據 this.rectData();//初始化矩形信息 } Rect.prototype.rectData = function(){ if(!!this.OperaArea == false){ console.log("無操作區域") return; } var rectObj = this.OperaArea.getElementsByClassName("rect"), item = null, rectData = {}; var len = rectObj.length; this.rectDataList = []; for(var i = 0; i < len; i++){ item = rectObj[i]; rectData = { id:item.getAttribute("id"), x:item.offsetLeft, y:item.offsetTop, w:item.clientWidth, h:item.clientHeight, xr:item.offsetLeft+item.clientWidth, yr:item.offsetTop+item.clientHeight, area:item.clientWidth*item.clientHeight } this.rectDataList.push(rectData) } } Rect.prototype.isIntersect = function(rect1,rect2){//判斷兩個矩形是否相交 var rect1CenterX = (rect1.xr+rect1.x)/2;//矩形1中心點坐標x var rect1CenterY = (rect1.yr+rect1.y)/2;//矩形1中心點坐標y var rect2CenterX = (rect2.xr+rect2.x)/2;//矩形2中心點坐標x var rect2CenterY = (rect2.yr+rect2.y)/2;//矩形2中心點坐標y var isX = Math.abs(rect2CenterX-rect1CenterX) < rect1.w/2 + rect2.w/2; var isY = Math.abs(rect2CenterY-rect1CenterY) < rect1.h/2 + rect2.h/2; return (isX && isY); } Rect.prototype.getIntersectArea = function(rect1,rect2){//求兩個矩形的相交面積 var rect3X = Math.max(rect1.x,rect2.x);//左上角X坐標 var rect3Y = Math.max(rect1.y,rect2.y);//左上角Y坐標 var rect3XR = Math.min(rect1.xr,rect2.xr);//右下角X坐標 var rect3YR = Math.min(rect1.yr,rect2.yr);//右下角Y坐標 var rect3W = rect3XR-rect3X;//寬度 var rect3H = rect3YR-rect3Y;//高度 return rect3W*rect3H; } Rect.prototype.isHaveReplaceRect = function(currentDrapRect,callback){//判斷當前拖拽矩形是否與窗體內矩形相交,如果相交且相交面積大於最小面積的3分之2,則放置 var len = this.rectDataList.length, item = null; for(var i = 0; i < len; i++){ item = this.rectDataList[i]; if(item.id == currentDrapRect.id){ continue; } if(this.isIntersect(item,currentDrapRect) == false){ continue; } var minArea = (currentDrapRect.area < item.area) ? currentDrapRect.area : item.area; if(this.getIntersectArea(item,currentDrapRect) < minArea*2/3){ continue; } !!callback ? callback(item.id) : ""; break; } }
DragDrop.js:
在之前的文章中有介紹過,現在只是優化了一下
window.DragDrop = function(){ var dragDrop = new EventTarget(),//自定義事件 dragSizing = null,//拖拽改變大小對象 dragSizeParent = null,//當前拖拽改變大小對象的父對象 dragging = null,//當前拖拽對象 drffWidth = 0,//當前拖拽改變大小對象的初始寬度 drffX = 0,//當前拖拽改變大小的時候 拖拽的起始點 diffX = 0,//表示拖拽點距離拖拽對象的內部距離 diffY = 0,//表示拖拽點距離拖拽對象的內部距離 draggingLeft = 0,//當前拖拽對象的left draggingTop = 0;//當前拖拽對象的Top var handleEvent = function(event){ event = EventUnit.getEvent(event);//獲取事件 var target = EventUnit.getTarget(event);//獲取事件對象 switch (event.type) { case "mousedown": if(target.className.indexOf("draggable") > -1){ EventUnit.preventDefault(event);//阻止默認行為 EventUnit.stopPropagation(event);//阻止冒泡 dragging = target.parentNode;//設置當前拖拽對象為事件對象的父元素 diffX = event.clientX - dragging.offsetLeft; diffY = event.clientY - dragging.offsetTop; //當前點相對於屏幕的位置減去當前點相對於拖拽對象的位置,就是當前點絕對定位該有的位置,這裏面減10是因為當前拖拽對象的margin是10 draggingLeft = (event.clientX - diffX-10) + "px"; draggingTop = (event.clientY - diffY-10) + "px"; var cssList = { "position":"absolute", "z-index":10, "opacity":0.7, "box-shadow":"0px 0px 5px #b5b5b5", "left":draggingLeft, "top":draggingTop }; DomUnit.cssText(dragging,cssList); dragDrop.fire({type:"dragstart",target:dragging,x:event.clientX,y:event.clientY}); return; } if(target.className.indexOf("dragsizeable") > -1){ EventUnit.preventDefault(event);//阻止默認行為 EventUnit.stopPropagation(event);//阻止冒泡 dragSizing = target;//設置當前拖拽大小對象為當前拖拽對象 dragSizeParent = dragSizing.parentNode;//設置當前拖拽大小對象父元素為當前拖拽對象父元素 drffWidth = dragSizeParent.clientWidth;//記錄改變寬度前的初始寬度 drffX = event.clientX;//記錄拖拽點改變前的初始拖拽X坐標 dragDrop.fire({type:"dragsizestart",target:dragSizing}); return; } break; case "mousemove": if(dragging != null){ EventUnit.preventDefault(event);//阻止默認行為 EventUnit.stopPropagation(event);//阻止冒泡 draggingLeft = (event.clientX - diffX-10) + "px"; draggingTop = (event.clientY - diffY-10) + "px"; var cssList = { "left":draggingLeft, "top":draggingTop }; DomUnit.cssText(dragging,cssList); //dragging.style.cssText="position:absolute;z-index:10;opacity:0.7;box-shadow:0px 0px 5px #b5b5b5;left:"+draggingLeft+";top:"+draggingTop+";"; dragDrop.fire({type:"drag",target:dragging,x:event.clientX,y:event.clientY}); return; } if(dragSizing != null){ EventUnit.preventDefault(event);//阻止默認行為 EventUnit.stopPropagation(event);//阻止冒泡 var distance = event.clientX - drffX;//拖拽距離 var width = drffWidth + distance;//最終寬度 dragSizeParent.style.width = width/dragSizeParent.parentNode.clientWidth*100+"%";//換算成百分比 dragDrop.fire({type:"dragsize",target:dragSizing}); return; } break; case "mouseup": if(!!dragging){ var cssList = { "position":"relative", "z-index":2, "opacity":1, "box-shadow":"none", "left":"auto", "top":"auto" }; DomUnit.cssText(dragging,cssList); dragDrop.fire({type:"dragend",target:dragging,x:event.clientX,y:event.clientY}); dragging = null; draggingLeft = 0; draggingTop = 0; return; } if(dragSizing != null){ dragDrop.fire({type:"dragsizeend",target:dragSizing}); dragSizing = null; dragSizeParent = null; drffWidth = 0; drffX = 0; return; } break; default: } } dragDrop.enable = function(){ EventUnit.addHandler(document,"mousedown",handleEvent); EventUnit.addHandler(document,"mousemove",handleEvent); EventUnit.addHandler(document,"mouseup",handleEvent); } dragDrop.disable = function(){ EventUnit.removeHandler(document,"mousedown",handleEvent); EventUnit.removeHandler(document,"mousemove",handleEvent); EventUnit.removeHandler(document,"mouseup",handleEvent); } dragDrop.enable(); return dragDrop; }();
DragTool.js:
最主要的js,實現拖動改變順序,拖拽改變寬度,本地保存順序及寬度信息
window.$$ = window.DragTool = (function(){ var DragTool = function(selector){ return new DragTool.prototype.init(selector); } DragTool.prototype = { constructor:DragTool, init:function(selector){ var dragArea = document.getElementById(selector) if(!dragArea){console.log("不存在的對象");return;} this.dragArea = dragArea; return this; } } DragTool.prototype.init.prototype = DragTool.prototype; //初始化拖拽信息 DragTool.prototype.initDrag = function(options){ if(!options.dragItemClass || !options.dragPlaceholderID){ return; } this.dragItem = options.dragItemClass;//可拖拽矩形類名 this.placeholderID = options.dragPlaceholderID;//當前拖拽的元素的占位符的ID this.placeholderEle = null;//表示當前拖拽元素的占位符對象 this.currentDragID = "";//表示當前拖拽元素的ID this.currentDragEle = null;//表示當前拖拽元素 this.rect = new Rect(this.dragArea);//創建矩形操作實例 this.dragCout = 0;//目的在於每一次拖拽都要觸發檢測方法,我們在這裏控制頻率 this.draggableIDList = []; this.dragEleIDListInit()//保存一份當前所有的可拖拽元素的ID,避免多次請求dom,造成的性能問題 this.initDragEvent(); } //創建占位符 DragTool.prototype.placeholderCreate = function(target){ var parentNode = target.parentNode;//當前元素的父元素 var nextNode = DomUnit.getNextNode(target);//當前元素的後一個元素 this.placeholderEle = document.createElement("span");//創建占位符 this.placeholderEle.setAttribute("id",this.placeholderID);//設置占位符ID DomUnit.cloneCssAttr(target,this.placeholderEle,[ {cssName:"margin",cssAttr:"marginBottom"}, {cssName:"height",cssAttr:"height"}, {cssName:"width",cssAttr:"width"}, {cssName:"background-color",cssAttr:"#e3e3e3",useCssAttr:true}, {cssName:"float",cssAttr:"left",useCssAttr:true}, {cssName:"box-sizing",cssAttr:"border-box",useCssAttr:true} ]);//將target的部分css屬性拷貝到占位符 this.currentDragID = target.getAttribute("id");//設置當前拖拽元素的ID this.currentDragEle = target; var targetNode = null; !!nextNode ? (targetNode = nextNode) : (targetNode = target); parentNode.insertBefore(this.placeholderEle,targetNode); this.putEleForTargetInElesList(this.placeholderID,targetNode.getAttribute("id"),0); this.removeEleFromElesList(this.currentDragID); } //將當前占位符移動到指定target前面 DragTool.prototype.putPlaceholderBeforeTarget = function(target){ var _this = this; if(!!target == false){return;} if(!!this.placeholderEle){ DomUnit.setEleBeforeTarget(this.placeholderEle,target,this.placeholderEle,1); this.putEleForTargetInElesList(this.placeholderID,target.getAttribute("id"),0); } } //將當前占位符移動到指定target後面,理論上插入在target前面只要調用insertBefore(ele,target.nextNode),調用本方法的前提是target是最後一個元素,而target.nextNode並不存在 DragTool.prototype.putPlaceholderAfterTarget = function(target){ if(!!target == false || !!this.placeholderEle == false){return;} var targetNextNode = DomUnit.getNextNode(target,this.currentDragID); if(!!targetNextNode){ this.putPlaceholderBeforeTarget(targetNextNode); return; } var targetParentNode = target.parentNode; targetParentNode.removeChild(this.placeholderEle); targetParentNode.appendChild(this.placeholderEle); this.putEleForTargetInElesList(this.placeholderID,target.getAttribute("id"),1); } //結束拖拽,將拖動對象移動到當前節點標誌位節點前面,並刪除拖拽占位符 DragTool.prototype.endDrag = function(){ if(!!this.placeholderEle == false){ return; } this.putEleBeforeTargetInElesListAndDelTarget(this.currentDragID,this.placeholderID); DomUnit.setEleBeforeTarget(this.currentDragEle,this.placeholderEle,this.placeholderEle,0); this.placeholderEle = null; this.currentDragID = ""; this.currentDragEle = null; } //初始化可拖拽對象ID數組 DragTool.prototype.dragEleIDListInit = function(){ if(!!this.dragArea == false){ console.log("無操作區域") return; } this.draggableIDList = []; var elements = this.dragArea.getElementsByClassName(this.dragItem); var len = elements.length; for(var i = 0; i < len; i++){ var item = elements[i]; this.draggableIDList.push(item.getAttribute("id")); } } //判斷a是否在b前面(draggableIDList操作) DragTool.prototype.isBeforeTargetInElesList = function(a,b){ var before = true; var aIndex = this.draggableIDList.indexOf(a); var bIndex = this.draggableIDList.indexOf(b); (aIndex < bIndex) ? (before = true) : (before = false); return before; } //將a插入在b前面或者後面,type 0前面,1後面(draggableIDList操作) DragTool.prototype.putEleForTargetInElesList = function(a,b,type){ var aIndex = this.draggableIDList.indexOf(a); var bIndex = this.draggableIDList.indexOf(b); if(bIndex == -1){ return; } this.removeEleFromElesList(a); if(aIndex != -1 && aIndex < bIndex){ bIndex = bIndex - 1; } if(type == 0){ this.draggableIDList.splice(bIndex,0,a); }else { this.draggableIDList.splice((bIndex+1),0,a); } } //將a插入在b前面,並刪除b DragTool.prototype.putEleBeforeTargetInElesListAndDelTarget = function(a,b){ var bIndex = this.draggableIDList.indexOf(b); if(bIndex == -1){ return; } this.draggableIDList.splice(bIndex,0,a); this.removeEleFromElesList(b); } //從數組中刪除a元素(draggableIDList操作) DragTool.prototype.removeEleFromElesList = function(a){ var aIndex = this.draggableIDList.indexOf(a); if(aIndex != -1){ this.draggableIDList.splice(aIndex,1); } } //初始化拖拽事件 DragTool.prototype.initDragEvent = function(){ var _this = this; DragDrop.addHandler("dragstart",function(event){ if(DomUnit.isParent(_this.dragArea,event.target) == false){return;} _this.dragCout = 0; _this.placeholderCreate(event.target); }); DragDrop.addHandler("drag",function(event){ if(DomUnit.isParent(_this.dragArea,event.target) == false){return;} _this.dragCout = _this.dragCout + 1; if(_this.dragCout%5 != 0){//控制頻率在每拖動5次觸發一次檢測 return; } var target = event.target; var currentDrapRect = { id:target.getAttribute("id"), x:target.offsetLeft, y:target.offsetTop, w:target.clientWidth, h:target.clientHeight, xr:target.offsetLeft+target.clientWidth, yr:target.offsetTop+target.clientHeight, area:target.clientWidth*target.clientHeight }//獲取當前拖拽的矩形信息 _this.rect.isHaveReplaceRect(currentDrapRect,function(rectID){ var target = document.getElementById(rectID); var isBeforeTarget = _this.isBeforeTargetInElesList(_this.placeholderID,rectID); if(isBeforeTarget){ var targetNextNode = DomUnit.getNextNode(target,_this.currentDragID); if(targetNextNode){ _this.putPlaceholderBeforeTarget(targetNextNode);//如果存在下一個元素,只插入下一個元素的前面,就是變相的插入此元素的後面 }else{ _this.putPlaceholderAfterTarget(target);//如果不存在下一個元素,說明是最後一個元素了,則直接插入在此元素的後面 } }else{ _this.putPlaceholderBeforeTarget(target);//插入在此元素的前面 } //重新刷新矩形數據 _this.rect.rectData() }); }); DragDrop.addHandler("dragend",function(event){ if(DomUnit.isParent(_this.dragArea,event.target) == false){return;} _this.endDrag()//結束拖拽的後續處理 _this.rect.rectData(); _this.dragEleIDListInit();//這句話是在拖拽結束後,重新初始化可拖拽數組,加上這句可防止異常造成的意外情況,不過會增加額外的dom操作 }); DragDrop.addHandler("dragsizeend",function(event){ if(DomUnit.isParent(_this.dragArea,event.target) == false){return;} _this.rect.rectData(); }); } return DragTool; })();
自定義模塊展示風格