手风琴效果

手风琴效果其实就是通过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.fromNodeList转换为数组,通常能尽量不用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会被忽略掉。