折叠菜单或者叫多级选项菜单,用流行UI框架下的相关实现改改就好了,为什么要手写?因为手痒 啊,有个简单方法特别想印证一下效果。前面的选项框就不做了,就是你点击了用一个数组去收集id之类的数据。我主要实现一下效果。
1.首先模拟的数据结构:
js
let [list, setList] = useState([{
title: "父标题111",
child: [{
title: '子标题1111'
}, {
title: '子标题1112',
child: [
{ title: '子标题11121' },
{
title: '子标题11122',
child: [
{ title: "子标题111221" },
{ title: "子标题111222" },
{ title: "子标题111223" },
{ title: "子标题111224" },
{ title: "子标题111225" },
{ title: "子标题111226" },
]
}
]
}, {
title: '子标题1113'
}]
},
{
title: "父标题222",
child: [{
title: '子标题2221'
}]
},
{ title: "父标题333" }
])
2.控制child显示隐藏可不是用display/visibility属性就行了,有动画效果要用transition和控制高度。用到的样式,一些想法都在代码注释里:
css
*{
margin: 0;
padding: 0;
}
.uuu{
transition:0.5s;
height:0; //一开始子项的ul列表要隐藏
overflow: hidden;
}
ul{
padding-left:20px;
}
3.视图起始
js
function App() {
...//上面的模拟数据
return (
<List list={list} />
)
}
4.List定义,设计上肯定是组件递归。
js
function List(props) {
return <ul className={[props.iso ? 'uuu' : '', 'out'].join(' ')} >
{props.list.map((t, i) => {
return <>
<li key={i} >
<div onClick={(e) => { t.child && an(e, t.child.length) }}>{t.title}</div>
{t.child && <List list={t.child} iso={true}></List>}
</li>
</>
})
}
</ul>
}
5.想法:点击title的div如果是显示子菜单,除了下一层ul要显示高度,它的所有上层都要增加这个高度,标识它可控外层的条件是增加了'out'这个class,父级没有这个class表示不是菜单本身不需要增加高度。
增加的iso={true}是什么,为什么视图起始没有?这个是控制子菜单一开始隐藏的,而最外层不需要隐藏。
6.核心代码。首先"展开"效果,上面有说,不仅本身高度展开,所有上层都要加这个高度:
js
let lout = (e, l) => {
if (e.className.indexOf('out') > -1) {
e.style.height = e.clientHeight + l + 'px'
lout(e.parentNode.parentNode, l)
} //点击的是div,它的上级是li,再上级才是ul。
}
7."折叠"效果,不仅折叠本身,它的所有子级都要折叠起来:
js
let lin = (e) => {
e.style.height = 0
e.childNodes.forEach(_e => {
if (_e.childNodes.length > 1) {
lin(_e.childNodes[1])
}
});
}
为什么要判断>1并且childNodes取第二项?因为有第一项div层。
8.控制逻辑。
js
let nHeight = 21
let an = (e, l) => {
let dom = e.currentTarget.nextSibling
let flag = dom.clientHeight == 0 ? false : true
if (flag) {
lout(dom, dom.clientHeight * (-1))
lin(dom)
} else {
lout(dom, l * nHeight)
}
}
如果当前菜单为折叠态,那就展开它和它的所有上层菜单;如果当前菜单是展开态,那不仅要折叠它和它的所有子级菜单,同时要把所有上层菜单也减少这个高度,也就是为什么要*(-1)。