> For the complete documentation index, see [llms.txt](https://nodelover.gitbook.io/web/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://nodelover.gitbook.io/web/7.-shou-feng-qin.md).

# 7.手风琴

手风琴效果其实就是通过JS改变元素的`height`，然后加上`transition`来让css动起来。

## 准备HTML结构

这里我们使用 `dl` ， 因为 `dt` 刚好可以模拟标题， `dd` 模拟内容。

```markup
    <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样式

```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` 代表下一个相邻的元素。

```javascript
        const dtDoms = document.querySelectorAll('dt');

        Array.from(dtDoms).forEach((dtDom) => {
            dtDom.onclick = (e) => {
                const dd = e.target.nextElementSibling; // 被点击的dt的下一个dd元素
                toggle(dd);
            }
        });
```

完成 `toggle` 函数

```javascript
        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`会被忽略掉。
