初学前端,因为写项目接触了饿了么的框架,它里面提供了树型组件,但无奈有时候可操控行并不是很高,于是,抱着学习加实用的态度,打算自己写一个。不多废话,直接上码。
以下为css代码:
.treeNode { position : relative ; margin-left : 20px ; } .treeNodeName:hover { background : azure ; cursor : pointer } .treeNodeContentDisplay { position : relative ; overflow : hidden } .treeNodeContentHidden { position : relative ; overflow : hidden }
其中treeNodeContentDisplay和treeNodeContentHidden这两个类名主要是为了方面标记树节点的展开和收起这两个状态。为了方便控制,所有元素均为相对定位。其中overflow:hidden是这里面的关键,它保证了滑动效果的正常(点开挤下去,收起又自动上来)。其实节点里的子元素是正常的,收起不显示是因为将包裹的外层元素高度设为了0,因为有溢出隐藏,所以就看不到了。如果没有溢出隐藏,那么虽然外包元素高度为0,但是里面的内容还是看的见,就很坑的会显示出来,然后下面的元素挤上来,就是重载一块儿了。
接下来就是整个树渲染实现部分了:
"user strict"; (function(global) { let makeTree = function(dom , data) { for(let i = 0 ; i < data.length ; ++i) { let treeNode = document.createElement("div"); treeNode.setAttribute("class" , "treeNode"); let nameNode = document.createElement("div"); nameNode.textContent = data[i]["name"]; nameNode.setAttribute("class" , "treeNodeName"); if(data[i]["action"]) { for(let j = 0 ; j < data[i]["action"].length ; ++j) { for(let key in data[i]["action"][j]) { nameNode.addEventListener(key , data[i]["action"][j][key]); } } } let contentNode = document.createElement("div"); contentNode.setAttribute("class" , "treeNodeContentHidden"); contentNode.style.height = "0px"; if(data[i]["children"]) { if(0 != data[i]["children"].length) { makeTree(contentNode , data[i]["children"]); } } treeNode.appendChild(nameNode); treeNode.appendChild(contentNode); dom.appendChild(treeNode); } }; let bindEvent = function(dom) { let childrenNodes = dom.children; for(let i = 0 ; i < childrenNodes.length ; ++i) { if("treeNode" == childrenNodes[i].getAttribute("class")) { let nameNode = childrenNodes[i].children[0]; let contentNode = childrenNodes[i].children[1]; nameNode.addEventListener("click" , function(){ let self = this; if("treeNodeContentHidden" == contentNode.getAttribute("class")) { if(self.hiddenInterval) { if(null != self.hiddenInterval) { clearInterval(self.hiddenInterval); } } self.displayInterval = setInterval(function(){ let totalHeight = 0; for(let j = 0 ; j < contentNode.children.length ; ++j) { totalHeight += contentNode.children[j].clientHeight; } if(totalHeight > contentNode.clientHeight) { contentNode.style.height = (contentNode.clientHeight + 1) + "px"; } else { contentNode.style.height = ""; clearInterval(self.displayInterval); } } , 4); contentNode.setAttribute("class" , "treeNodeContentDisplay"); } else { if(self.displayInterval) { if(null != self.displayInterval) { clearInterval(self.displayInterval); } } self.hiddenInterval = setInterval(function(){ if(0 < contentNode.clientHeight) { contentNode.style.height = (contentNode.clientHeight - 1) + "px"; } else { contentNode.style.height = "0px"; clearInterval(self.hiddenInterval); } } , 4); contentNode.setAttribute("class" , "treeNodeContentHidden"); } }); bindEvent(contentNode); } } }; function DirectoryTree() { } DirectoryTree.prototype.createTree = function(dom , data) { dom.style.marginLeft = "-20px"; dom.style.position = "relative"; makeTree(dom , data); bindEvent(dom); }; global.DirectoryTree = new DirectoryTree(); })(window);
该部分代码实现了树的渲染和事件的响应,内部方法makeTree和bindEvent通过递归的方式将整棵树渲染出来并绑定相关事件。其中,整个树的滑动效果是通过定时器来进行的,当然,为了不浪费资源,我们需要在动画结束后关闭定时器,这里我们将定时器的信息保存成dom对象的一个属性,方便后面使用。但是这里需要注意,因为定时器内this指向会发生改变(指向window),所以在点击方法内将指向dom的this保存到self。对于外部,通过立即函数将DirectoryTree实例添加至window对象,使其成为一个全局的对象,方便调用。定义createTree在DirectoryTree原型链上供外部调用,createTree需要两个参数,一个为放置树的dom对象,该对象可以为任意,只要能有子元素的标签都可以;另一个为treeData,为树的信息结构,表现如下:
let treeData = [ { name : "ddd" , action : [ {mouseover : function(){console.log(1)}} , {click : function(){console.log(2)}} ] , children : [ { name : "ccc" , children : [ { name : "sgf" , children : [] } , { name : "dffgsf" , children : [] } , { name : "dfgh" , children : [] } ] } , { name : "gsdf" , children : [ { name : "4352" , children : [ { name : "dfads" , children : [] } ] } ] } , { name : "fda" , children : [] } ] } , { name : "lalala" , children : [ { name : "dfldkjfl" } , { name : "dfl" } , { name : "ldsfkjl" } ] } ];
其中,name为树的节点名,是必须项;action为节点的响应事件,为一个数组,可以有多个事件响应,每个响应为一个key值为事件,值为响应方法的对象。该参数不是必须的;children为该节点下的子节点,为一个数组,元素为节点信息,该项不是必须项。
以下为html测试部分:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>DirectoryTreeTest</title> <link rel = "stylesheet" href = "./DirectoryTree.css" /> <script type = "text/javascript" src = "./DirectoryTree.js"></script> </head> <body> <div id = "tree"></div> <script> let treeData = [ { name : "ddd" , action : [ {mouseover : function(){console.log(1)}} , {click : function(){console.log(2)}} ] , children : [ { name : "ccc" , children : [ { name : "sgf" , children : [] } , { name : "dffgsf" , children : [] } , { name : "dfgh" , children : [] } ] } , { name : "gsdf" , children : [ { name : "4352" , children : [ { name : "dfads" , children : [] } ] } ] } , { name : "fda" , children : [] } ] } , { name : "lalala" , children : [ { name : "dfldkjfl" } , { name : "dfl" } , { name : "ldsfkjl" } ] } ]; DirectoryTree.createTree(document.querySelector("#tree") , treeData); </script> </body> </html>初学初写,望加指正