自定义模块展示风格
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义模块展示风格相关的知识,希望对你有一定的参考价值。
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; })();
以上是关于自定义模块展示风格的主要内容,如果未能解决你的问题,请参考以下文章