树视图是仿 https://webpack.js.org/concepts/ 做的
图标用的是阿里的iconfont,http://www.iconfont.cn/
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<style>
@font-face { font-family: 'icons'; src: url('data:application/font-woff;charset=utf-8;base64,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') format('woff'); }
* {
font-size: 16px;
line-height: 2;
font-weight: 600;
margin: 0;
padding: 0;
/*
(1)content-box
width 和 height 只包括内容(默认)
(2)border-box
width 和 height 属性包括内容,内边距和边框,但不包括外边距。
*/
box-sizing: border-box;
}
ul {
list-style:none;
}
a {
/* 去掉默认的下划线 */
text-decoration: none;
color: #535353;
/* 属性改变时,需要执行过渡动画的属性名和动画时间 */
transition: color 1000ms;
pointer-events: auto;
}
a:hover{
color: #144f80;
}
li {
/*
由于伪元素不是一个DOM对象,所以它不能绑定事件,因此为了绑定事件只能借助其父元素
pointer事件包括了mouse、touch、pen。
pointer-events:auto | none | visiblepainted | visiblefill | visiblestroke | visible | painted | fill | stroke | all
(1)auto:与pointer-events属性未指定时的表现效果相同。在svg内容上与visiblepainted值相同
(2)none:元素永远不会成为鼠标事件的target。但是,当其后代元素的pointer-events属性指定其他值时,鼠标事件可以指向后代元素,在这种情况下,鼠标事件将在捕获或冒泡阶触发父元素的事件侦听器。
其他值只能应用在SVG上。
*/
pointer-events: none;
}
li::before {
pointer-events: auto;
}
.treenode-expandable::before {
/* !import是一个增加样式权重的方法,让浏览器首选执行这个语句 */
font-family: icons !important;
content: '\F103';
cursor: pointer;
color: #175d96;
margin-right: 0.5em;
position: absolute;
top: 0.1em;
left: 0.3em;
}
.treenode-expandable {
position: relative;
}
/*
elementA>elementB 表示选择父元素为A的所有子元素B
elementA elementB 表示选择祖先元素为A的所有子元素B
*/
.treenode-expandable>a {
margin-left: 1.5em;
}
.treenode-expandable>ul {
margin-left: 1.5em;
position: relative;
}
.treenode-expandable>ul::before {
/* 如果不设置content伪元素就不会生效 */
content: '';
/* absolute的是相对relative的父对象进行定位的。注意:position的默认值为static */
position: absolute;
height: calc(100% - 1em);
border-left: 1px dashed #777676;
}
.treenode::before {
content: '';
position: absolute;
width: 1em;
top: 1em;
border-top: 1px dashed #777676;
}
.treenode {
position: relative;
}
.treenode>a {
margin-left: 1.5em;
}
</style>
<script>
//这是一个map对象,map的key和value用方括号包裹,并用逗号分隔
const listData = new Map([
[{text: '第1个标签', href: '#1'},
[
[{text: '子节点 1', href: '#1.1'}],
[{text: '子节点 2', href: '#1.2'}, [
[{text: '子节点 1', href: '#1.2.1'}],
[{text: '子节点 2', href: '#1.2.2'}],
[{text: '子节点 3', href: '#1.2.3'}]]
],
[{text: '子节点 3', href: '#1.3'}],
]
],
[{text: '第2个标签', href: '#2'},
[
[{text: '子节点 1', href: '#2.1'}],
[{text: '子节点 2', href: '#2.2'}],
[{text: '子节点 3', href: '#2.3'}],
]
],
[{text: '第3个标签', href: '#3'},
[
[{text: '子节点 1', href: '#3.1'}],
[{text: '子节点 2', href: '#3.2'}],
[{text: '子节点 3', href: '#3.3'}],
]
]
]);
function createList(listData) {
const list = document.createElement('ul');
for(const data of listData) {
const li = document.createElement('li');
const a = document.createElement('a');
a.innerText = data[0].text;
a.setAttribute('href', data[0].href);
li.appendChild(a);
const hasChild = (data.length > 1);
if(hasChild) {
li.appendChild(createList(data[1]));
}
li.setAttribute('class', 'treenode' + (hasChild ? '-expandable' : ''));
list.appendChild(li);
}
return list;
}
window.onload = () => {
document.body.appendChild(createList(listData));
//querySelectorAll会查找元素内部匹配条件的所有元素,而querySelector只会查找元素内部第一个匹配条件的元素
document.querySelectorAll('.treenode-expandable', '::before').forEach(node => {
node.onclick = (event)=> {
//event.target指的是触发事件的元素源,localName用来获取触发事件元素的标签
switch(event.target.localName) {
case "li":
const ul = node.querySelector('ul');
//setAttribute用来设置元素的属性,而display是style对象内的属性
ul.style.display = (ul.style.display == "" ? "none" : "");
break;
case "a":
//preventDefault方法用来阻止元素发生默认的行为(点击a标签默认会转向锚点链接)
//event.preventDefault();
break;
}
//由于事件会逐层向上传递(事件冒泡),因此必须调用该方法阻止事件继续传播
event.stopPropagation();
}
});
}
</script>
</head>
<body>
</body>
</html>