8102 年,用 CSS 实现折叠面板的动画

2018/05/26

作为一个经常写后端代码的程序员,偶尔写写前端代码也是勉强可以应付的。最近在重构某网站的时候,需要重新实现一个折叠面板。因为用了 Angular 这样酷炫的前端框架,我相信,就算没有 jQuery 这样的工具,手撸一个折叠面板应该没啥问题。就当我按照自己的直觉写完第一版代码之后,简单的运行了一下,我就发现事情好像并没有那么简单。

如果你愿意动手尝试的话,可以把下面的代码复制到 这里,在线预览效果

// v1 in less
.panel-content {
    margin: .4rem 1.2rem;
    overflow: hidden;
    transition: height .5s;
    &.collapsed {
        height: 0;
    }
    &.expanded {
        height: auto;
    }
}

我通过 JavaScript 让一个 div 的类在 collapsedexpanded 之间切换。折叠的时候设置元素高度为 0,展开的时候设置高度为 auto。这样运行出来的效果就是,虽然面板在展开与折叠状态之间切换了,但是并没有出现我想要的动画效果。经过一番检索,我发现,即使已经 8102 年了,我们还是无法为 height: 0height:auto 设置 transition

经过爆栈网上大佬的指点,我们可以使用 max-height 来实现这样的折叠效果:https://stackoverflow.com/a/8331169/6120806 。然后我就写出了第二版 LESS:

// v2 in less
.panel-content {
  background:tan;
  margin-top: 10px;
  overflow: hidden;

  &.collapsed {
    transition: max-height 0.50s;
    max-height: 0;
  }

  &.expanded {
    max-height: 9999px;
    transition: max-height 0.50s;
  }
}

这里有一个需要注意的地方,在展开状态下,需要把 max-height 设置为一个较大的值,避免面板内部的元素因为溢出而被隐藏。这下,动画效果就有了。

不过要是只有这么一点坑是对不起这个标题的,如果你使用的是第二版的代码,那么你就会发现一个诡异的问题,当收起面板的时候,会产生明显的延迟,跟那个答案中的演示效果出入很大,难道是因为他用的是 :hover 触发动画而我使用切换 class 触发动画这点不同吗?

再次经过一番检索,在爆栈网发现了对应的解决方案:https://stackoverflow.com/a/39103740/6120806。如果要通过切换 class 实现动画,我们需要显式的为收起的动画指定一个时间函数 cubic-bezier(0, 1, 0, 1),于是便有了下面的第三版。

.panel-content {
  background:tan;
  margin-top: 10px;
  overflow: hidden;

  &.collapsed {
    transition: max-height 0.50s cubic-bezier(0, 1, 0, 1);
    max-height: 0;
  }

  &.expanded {
    max-height: 999px;
    transition: max-height 0.50s cubic-bezier(1, 0, 1, 0);
  }
}

这下已经接近完美了,展开与收起的动画都十分流畅,没有任何延迟。不过,我看着这个动画突然感觉有些不对劲,为什么收起的时候动画的速度会感觉比较慢而展开的时候速度那么快?后来仔细阅读了上面提到的 Issue 下面的讨论,找到了答案:因为我们把展开状态下的 max-height 设置的太大了,所以 transition 中设置的动画时间并不是我们观察到的动画时间。为了应对这个问题,我们需要稍微延长一下展开的动画时间。

虽然已经 8102 年了,但是自己实现一个折叠面板,确实不太容易啊。

内容导航