当我们想给一个容器设置展开折叠动画时,很自然地想到使用 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';
});
如代码所示,为了在展开时设置容器的高度,在 show 为 true 时,先获取容器的高度并存在 boxHeight 中。
通过代码 box.style.height = `${show ? boxHeight : 0}px`;,我们就预先给容器设置了高度。然后通过 requestAnimationFrame,在下一帧设置容器动画结束后的高度,这样子就可以实现过渡动画效果了。
最后,由于窗口缩放等原因,容器高度可能会发生改变,导致 boxHeight 的值可能会小于或大于容器实际的高度。所以,在动画过渡结束后,且为展开时,将容器的高度设置为 auto,以避免这种情况。
另外,由于过渡动画的实现,依赖于容器一开始为展开显示时提前获取的容器高度,所以如果容器一开始就是折叠的情况,容器高度就获取不到了,容器无法展开,动画也就无法实现了。这种情况我们可以给 boxHeight 提前设置一个大概的高度值:let boxHeight = 200;,尽管不准确,但是由于在展开动画过渡结束后,会将容器的高度设置回 auto,所以容器最后也能展示完全,而且除了第一次展开,之后的展开都能设置为正确的值。