使用 CSS + JS,最少代码实现不定高度容器的展开折叠动画

549 阅读2分钟

当我们想给一个容器设置展开折叠动画时,很自然地想到使用 CSS,设置 transition: height 0.3s 实现。但对于高度不确定的容器而言,transition 动画是不生效的。

这是由于 transition 属性要求明确的开始和结束状态,如果高度是自动计算的,那么浏览器无法插入动画,因为它无法明确知道动画开始和结束的值。

那么为了解决这个问题,我们可以在动画开始之前,为该容器预先设置一个高度值,然后再开始动画。

首先我们先实现一个不定高度的容器,其高度由其子元素决定:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #box {
        transition: height 0.3s;
        overflow: hidden;
      }
      .list {
        display: flex;
        flex-wrap: wrap;
      }
      .item {
        flex: 1 0 200px;
        height: 30px;
        margin: 10px;
        background-color: skyblue;
      }
    </style>
  </head>
  <body>
    <div id="box">
      <div class="list">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
      </div>
    </div>
    <button id="btn">展开/折叠</button>
    <script>
      const btn = document.getElementById('btn');
      const box = document.getElementById('box');
      
      // code ...
    </script>
  </body>
</html>

然后,监听 btn 的点击事件:

let show = true;
let boxHeight = 0;
btn.addEventListener('click', () => {
    if (show) {
      const h = box.offsetHeight;
      if (h) boxHeight = h;
    }

    box.style.height = `${show ? boxHeight : 0}px`;

    requestAnimationFrame(() => {
      box.style.height = `${show ? 0 : boxHeight}px`;
      show = !show;
    });
  });
  
  box.addEventListener('transitionend', () => {
    if (show) box.style.height = 'auto';
  });

如代码所示,为了在展开时设置容器的高度,在 showtrue 时,先获取容器的高度并存在 boxHeight 中。

通过代码 box.style.height = `${show ? boxHeight : 0}px`;,我们就预先给容器设置了高度。然后通过 requestAnimationFrame,在下一帧设置容器动画结束后的高度,这样子就可以实现过渡动画效果了。

最后,由于窗口缩放等原因,容器高度可能会发生改变,导致 boxHeight 的值可能会小于或大于容器实际的高度。所以,在动画过渡结束后,且为展开时,将容器的高度设置为 auto,以避免这种情况。

另外,由于过渡动画的实现,依赖于容器一开始为展开显示时提前获取的容器高度,所以如果容器一开始就是折叠的情况,容器高度就获取不到了,容器无法展开,动画也就无法实现了。这种情况我们可以给 boxHeight 提前设置一个大概的高度值:let boxHeight = 200;,尽管不准确,但是由于在展开动画过渡结束后,会将容器的高度设置回 auto,所以容器最后也能展示完全,而且除了第一次展开,之后的展开都能设置为正确的值。