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

准备HTML结构

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

  1. <div class="panel">
  2. <dl>
  3. <dt>One Item</dt>
  4. <dd>One Item Content .</dd>
  5. <dt>two Item</dt>
  6. <dd>two Item Content .</dd>
  7. <dt>3 Item</dt>
  8. <dd>3 Item Content .</dd>
  9. <dt>4 Item</dt>
  10. <dd>4 Item Content .</dd>
  11. </dl>
  12. </div>

准备css样式

  1. *{margin: 0;padding: 0;}
  2. .panel{
  3. width: 480px;
  4. min-height: 160px;
  5. margin: 50px auto;
  6. background: #eee;
  7. }
  8. .panel dt{
  9. min-height: 40px;
  10. line-height: 40px;
  11. background: #f9f9f9;
  12. padding-left: 10px;
  13. cursor: pointer;
  14. }
  15. .panel dd{
  16. padding-left: 10px;
  17. height: 0;
  18. overflow: hidden;
  19. transition: height .5s;
  20. }

接下来书写我们的JS逻辑

首先我们为我们每一个 dt 绑定 click 事件

同样,我们使用 Array.fromNodeList转换为数组,通常能尽量不用for的我就不会用for

nextElementSibling 代表下一个相邻的元素。

  1. const dtDoms = document.querySelectorAll('dt');
  2. Array.from(dtDoms).forEach((dtDom) => {
  3. dtDom.onclick = (e) => {
  4. const dd = e.target.nextElementSibling; // 被点击的dt的下一个dd元素
  5. toggle(dd);
  6. }
  7. });

完成 toggle 函数

  1. function toggle(target){
  2. const ddDoms = document.querySelectorAll('dd');
  3. if(target.style.height == '' || target.style.height == '0px' ) { // 第一次的时候 height 为 ‘’ 空字符串.
  4. for(dd of ddDoms){
  5. dd.style.height = '0px';
  6. }
  7. Object.assign(target.style, {
  8. position: "absolute",
  9. left: "-2000px",
  10. top: "-2000px",
  11. height: "auto",
  12. width: "480px"
  13. });
  14. const height = target.offsetHeight;
  15. target.style.cssText = 'height: 0px';
  16. requestAnimationFrame(() => {
  17. target.style.cssText = 'height: ' + height + 'px';
  18. })
  19. }else{
  20. target.style.height = '0px';
  21. }
  22. }

首先为我们要拿到所有的 dd 方便之后重置样式。 然后我们判断目标元素target上面的height, 初始的时候是 ''空字符串,之后就是0px了,当是0px的时候,就说明是关闭状态。

之后我们通过for of遍历所有 dd, 先清空一下未关闭的标签。

然后我们可以通过 Object.assign 来把后面的对象上面的属性拷贝到 target.style 上面去。

通常我们是无法获取一个隐藏元素的高的,所以我们首先把当前的 dd, 用绝对定位定位到屏幕外面去,之后我们就可以通过offsetHeight获取高度了。

有的人会问我为什么不直接用auto, 因为auto是不会产生动画的。 当然你也可以设置一个固定的高度。

之后我们,先把高度重置为0,之后在浏览器重新绘制下一帧的时候再设置高度。

你可以尝试一下去掉requestAnimationFrame,你同样会发现动画失效了。

我猜测可能是浏览器做了处理,短时间的切换属性height会被忽略掉。