Appearance
使css高度auto支持过渡动画
前言
在平时的开发中,我们经常遇见各种展开与收起的场景,例如子菜单,手风琴,提示框,下拉框的等,这种效果通常都是利用transition
过渡高度实现的,但众所周知,height
和width
要支持transition
过渡动画必须设置成具体的数值,要想让高不定或者设置成auto
的元素支持过渡动画,我收集了三种实现方法:
max-height过渡
其一是设置最大或高度,将max-height
设置成一个元素高度基本不可能达到的值,然后transition
过渡max-height
;
效果如下:
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
这样实现的过渡缺点是很明显的:由于过渡过程是按照最大高度过渡的,假如max-height
为1000px,元素实际高度为300px并且过渡时长为1s时,元素在0.3s的时候就已经展开完成了,这就显得展开过程很快,而当收回的时候,点击按钮会等待0.7s元素才开始动画,有很明显的延迟。
配合js实现过渡
其二实现方式是配合js,在元素展开状态改变后元素还没来得及渲染时,获取元素高度,并设置给元素从而实现动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
点击查看源码
vue
<script setup lang="ts">
import {ref} from "vue";
const boxRef = ref();
const isExpand = ref(false);
const duration = 200;
const toggle = () => {
isExpand.value = !isExpand.value;
if (isExpand.value) {
boxRef.value.style.height = 'auto';
requestAnimationFrame(() => {
//在设置height:auto后下一帧获取元素高度
const height = boxRef.value.getBoundingClientRect().height || 0;
//先将元素高度设置为0,再在下一帧设置为上一步获取的高度,这样可以实现0-height的transition
boxRef.value.style.height = '0px';
requestAnimationFrame(() => {
boxRef.value.style.height = height + 'px';
//在动画结束后,再将元素高度设置为auto
setTimeout(() => {
boxRef.value.style.height = 'auto';
}, duration);
})
})
} else {
//先获取元素高度,再在下一帧设置为0,这样可以实现height-0的transition
const height = boxRef.value.getBoundingClientRect().height || 0;
boxRef.value.style.height = height + 'px';
requestAnimationFrame(() => {
boxRef.value.style.height = '0px';
})
}
}
</script>
<template>
<el-button type="primary" class="button" @click="toggle">点击{{isExpand?'收起':'展开'}}</el-button>
<div class="box" ref="boxRef" :style="{'transition-duration':duration+'ms'}">
<div class="content">
<div v-for="item in 10">使css高度auto支持过渡动画</div>
</div>
</div>
</template>
<style scoped lang="scss">
.button {
margin-bottom: 20px;
}
.box {
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
transition: height 0.5s;
overflow: hidden;
height: 0;
.content {
padding: 10px;
}
}
</style>
通过js配合transition
成功的实现了元素不定高度的过渡动画,但是可以看到,整个实现过程是很复杂的,而且必须完全理解动画实现的每一个步骤才能实现。
利用grid布局中的fr单位实现过渡
有没有一种不用js存粹使用css的实现方法呢?还真有,便是接下来介绍的使用gird布局中的fr实现过渡。
gird布局中有一个全新的单位fr
,用于定义网格轨道大小的弹性系数。grid布局比较复杂,三言两语是说不清楚的,有兴趣的同学可自行查阅相关文档,但在这里我们仅仅需要知道的是gird的基础排列方式以及fr
单位的作用。transition
只能过渡设置具体数值的属性,而fr
单位也是一个具体的数值,并且它的作用最直观的体现就是能影响元素的宽高,所以在此我们可以利用fr
来实现动画的过渡。
首先我们用grid编写出这样一个布局:
主要代码如下:
html
<div class="grid">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
css
.grid{
display: grid;
height: 300px;
grid-template-rows: 1fr 1fr 1fr;
}
这里的grid-template-rows
表示布局中元素行方向上排列方式,属性值1fr 1fr 1fr
表示每一个item占有1等分剩余高度空间,也就是每个item分得100px。
将grid-template-rows
设置为1fr 2fr 1fr
时,效果如下:
将grid-template-rows
设置为1fr 0fr 1fr
时,效果如下:
可以看到,中间的item设置为2fr
时,元素的高度为150px,也就是父元素.grid
高度的一半,这表示该元素分得的剩余高度为2 / (1 + 2 + 1) = 0.5,当设置为0fr
的时候,元素高度变成了0,分得的剩余空间0 / (1 + 0 + 1)也等于0。
此时,将grid-template-rows
设置上过渡,并加上展开与收起状态控制:
css
.grid{
transition: grid-template-rows 0.3s;
}
中间的item像一张大口张开闭合,元素从1fr
到0fr
实现了过渡。
再将上下两个item去掉,将父元素grid-template-rows
改为1fr
并不设置高度,就实现了元素的展开与收起
使css高度auto支持过渡动画
为什么不起作用呢?其实这是由grid的最小尺寸规则决定的,此时的最小高度是min-content
,也就是由内部文本决定的,而父元素能撑开的高度也是文本高度,所以就没有出现收缩效果,这时我们可以将item的overflow
设置为hidden
,超出部分隐藏,使得最小高度为0,保险起见,父元素overflow
也设置为hidden
,这样就能使父元素收回到0。
使css高度auto支持过渡动画
gird
还有grid-template-columns
属性,表示设置列方向上子元素剩余空间,按照上面的方法,我们可以实现宽度的收缩与展开:
使css高度auto支持过渡动画
细心的同学可能会发现,上面的例子还是有些不足的:border
在收起后并没有被收进去,宽度的过渡会使文字在过渡过程中不断重新换行,我的解决方案是,再将item包裹一层元素,border
设置到item元素上,不设置到这层元素,文字包裹进一层文字层包裹层,文字包裹层设置固定宽度(好像这又回到最初的亚子,其实也可以宽度直接固定数值过渡,我在这里为方便便顺便实现了),padding值也设置到文字包裹层,便可同时实现宽高的过渡动画。
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
使css高度auto支持过渡动画
总结
到此,我便介绍完了三种使css高度auto支持过渡动画的方法,三种方法各有优劣,max-height
/max-widht
,简单实用但效果不佳,js配合实现可靠兼容性好但过程难懂,代码复杂grid
的fr
只用css实现但grid
属性兼容性没那么好,如果不考虑兼容性问题的话,我推荐使用第三种方法,在vue中,也可以将其封装成一个组件,方便调用,这里可以参考我自己封装的一个组件:CollapsibleBox.vue