手风琴效果其实就是通过JS改变元素的height
,然后加上transition
来让css动起来。
准备HTML结构
这里我们使用 dl
, 因为 dt
刚好可以模拟标题, dd
模拟内容。
<div class="panel">
<dl>
<dt>One Item</dt>
<dd>One Item Content .</dd>
<dt>two Item</dt>
<dd>two Item Content .</dd>
<dt>3 Item</dt>
<dd>3 Item Content .</dd>
<dt>4 Item</dt>
<dd>4 Item Content .</dd>
</dl>
</div>
准备css样式
*{margin: 0;padding: 0;}
.panel{
width: 480px;
min-height: 160px;
margin: 50px auto;
background: #eee;
}
.panel dt{
min-height: 40px;
line-height: 40px;
background: #f9f9f9;
padding-left: 10px;
cursor: pointer;
}
.panel dd{
padding-left: 10px;
height: 0;
overflow: hidden;
transition: height .5s;
}
接下来书写我们的JS逻辑
首先我们为我们每一个 dt 绑定 click
事件
同样,我们使用 Array.from
将 NodeList
转换为数组,通常能尽量不用for
的我就不会用for
。
nextElementSibling
代表下一个相邻的元素。
const dtDoms = document.querySelectorAll('dt');
Array.from(dtDoms).forEach((dtDom) => {
dtDom.onclick = (e) => {
const dd = e.target.nextElementSibling; // 被点击的dt的下一个dd元素
toggle(dd);
}
});
完成 toggle
函数
function toggle(target){
const ddDoms = document.querySelectorAll('dd');
if(target.style.height == '' || target.style.height == '0px' ) { // 第一次的时候 height 为 ‘’ 空字符串.
for(dd of ddDoms){
dd.style.height = '0px';
}
Object.assign(target.style, {
position: "absolute",
left: "-2000px",
top: "-2000px",
height: "auto",
width: "480px"
});
const height = target.offsetHeight;
target.style.cssText = 'height: 0px';
requestAnimationFrame(() => {
target.style.cssText = 'height: ' + height + 'px';
})
}else{
target.style.height = '0px';
}
}
首先为我们要拿到所有的 dd 方便之后重置样式。 然后我们判断目标元素target
上面的height
, 初始的时候是 ''
空字符串,之后就是0px
了,当是0px
的时候,就说明是关闭状态。
之后我们通过for of
遍历所有 dd, 先清空一下未关闭的标签。
然后我们可以通过 Object.assign 来把后面的对象上面的属性拷贝到 target.style 上面去。
通常我们是无法获取一个隐藏元素的高的,所以我们首先把当前的 dd, 用绝对定位定位到屏幕外面去,之后我们就可以通过offsetHeight
获取高度了。
有的人会问我为什么不直接用auto
, 因为auto
是不会产生动画的。 当然你也可以设置一个固定的高度。
之后我们,先把高度重置为0,之后在浏览器重新绘制下一帧的时候再设置高度。
你可以尝试一下去掉requestAnimationFrame
,你同样会发现动画失效了。
我猜测可能是浏览器做了处理,短时间的切换属性height
会被忽略掉。