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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://nodelover.gitbook.io/web/7.-shou-feng-qin.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
