如何实现手风琴动画?
如果让你用CSS实现一个手风琴时的动画效果,例如:
你会怎样实现?我的第一想法是:这还不简单,用 transition: height
不就能轻易实现了吗?给隐藏的内容设置初始高度 height : 0
,当其展开时再动态给 height
赋值,height
变化时应该就可以触发 transition
的过渡动画了。
根据这个思路,以下是用 vue 编写的示例代码:
js
<template>
<button @click="toggle">button</button>
<div id="expandable" :class="show? 'show': 'hidden'">
<div>Qui tempor Lorem cillum do ex nisi voluptate consequat cupidatat.</div>
</div>
<div>Cillum proident Lorem Lorem nisi aliqua magna Lorem labore laboris mollit.</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const show = ref(false);
function toggle() {
show.value = !show.value;
}
</script>
<style scoped>
#expandable {
overflow: hidden;
transition: height 1s ease;
}
#expandable.show {
height: 100%;
}
#expandable.hidden {
height: 0;
}
</style>
让我们看看效果:
嗯?怎么和我想得不一样,我动画呢?
动画未生效的原因是 transition 的机制:
Transitions enable you to define the transition between two states of an element.
简单来说,Transition 动画只能在两个确定值之间触发,在上面的例子中,height : 0
是一个确定值,但 height : 100%
可不是,如果我们将 height : 100%
换成如 height : 20px
,则动画就可以正常生效了:
但 20px
只是一个我随机指定的值,要获得准确的高度,还需要使用 Js 来搜索隐藏元素,获取其真实高度,并动态设置其 height
属性。因为这不是本文的主题,此处不再详述。
那么,只使用 Css 要如何实现这个效果呢?以下的这串简单的 css 代码可以神奇的帮我们实现这个功能:
css
#expandable {
display: grid;
overflow: hidden;
transition: grid-template-rows 1s ease;
}
#expandable.show {
grid-template-rows: 1fr;
}
#expandable.hidden {
grid-template-rows: 0fr;
}
#expandable div {
min-height: 0;
}
好的。本文到此结束,感谢您的阅读,我们下期再见。
...
:D,开玩笑的, grid-template-rows: 0fr
是啥意思? 为啥要使用 min-height:0
而不是 height : 0
? 我们慢慢来解释。
grid-template-rows:0fr
学习的最好办法是从熟悉的东西入手,因此,讨论 0fr
可以从 1fr
开始,讨论 grid-template-rows
可以从平时用的更多的 grid-template-columns
入手。
fr
的意思是 「fraction 分数、部分」,grid-template-columns: 1fr
表示将剩余的空间分为 1 份,每个元素占据 1 份(全部)的空间:
js
<template>
<div id="wrapper">
<div id="inner">
Lorem labore excepteur eiusmod anim.
</div>
</div>
</template>
<style scoped>
#wrapper {
display: grid;
grid-template-columns: 1fr;
border: 1px solid #000;
}
</style>
而如果 fr
的值小于 1,如 0.5fr
,则表示将剩余的空间分为 1 份,每个元素占据 0.5 份空间,剩余空间留白。
css
<style scoped>
#wrapper {
display: grid;
grid-template-columns: 0.5fr;
border: 1px solid #000;
}
</style>
如果我们继续减少 fr
的值,如 0.01fr
,这是否意味着栅格所占空间也将继续减少到 0.01
呢?
css
<style scoped>
#wrapper {
display: grid;
grid-template-columns: 0.01fr;
border: 1px solid #000;
}
</style>
然而并没有。栅格的宽度固定在了最长的单词 excepteaur 的宽度。我们只有使用 min-height/width
才能将这个空间继续缩小。
css
<style scoped>
#wrapper #inner {
min-width: 0;
}
</style>
根据上面的结果,我们有理由进行推理:如果 0.01fr
继续缩小,则栅格所占据的空间会继续缩小,最后 0fr
+ min-height : 0
将使占据空间缩小为0。
关于 grid
, fr
与 min-height/width
,以下是较为直观的解释:
1fr
表示占据主轴中尽量多的空间。0fr
表示占据主轴中尽量少的空间 。这个空间可看作min-height/width
,min-height/width
的默认值为 auto ,其一般为一个字母/一个单词所占空间。当我们使用min-height : 0
时,等于覆盖了初始的auto
,声明主轴所占据的最小空间为0.
因此:
grid-template-columns: 1fr;
表示容器内的每个栅格占满宽度 (width : 100%
),高度为 100%grid-template-columns: 0fr;
表示容器内的每个栅格占尽量少的宽度 (min-width
),高度为 100%grid-template-rows: 1fr;
表示容器内的每个栅格占满高度(height : 100%
),宽度为 100% .grid-template-rows: 0fr;
表示容器内的每个栅格占尽量少的高度,若min-height : 0
,则表示每个栅格所占高度为0,宽度为 100% .
现在让我们回顾最开始的代码:
js
<template>
<button @click="toggle">button</button>
<div id="expandable" :class="show? 'show': 'hidden'">
<div>Qui tempor Lorem cillum do ex nisi voluptate consequat cupidatat.</div>
</div>
<div>Cillum proident Lorem Lorem nisi aliqua magna Lorem labore laboris mollit.</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const show = ref(false);
function toggle() {
show.value = !show.value;
}
</script>
<style scoped>
#expandable {
display: grid;
overflow: hidden;
transition: grid-template-rows 1s ease;
}
#expandable.show {
grid-template-rows: 1fr;
}
#expandable.hidden {
grid-template-rows: 0fr;
}
#expandable div {
min-height: 0;
}
</style>
当 show = false
时:
- 容器设置:
grid-template-rows: 0fr
,表示其内元素占尽量少的高度。 - 给折叠面板元素设置
min-height : 0
,表示其占据的尽量少的高度为0。 - 容器设置 :
overflow:hidden
,表示隐藏折叠面板。
当 show = true
时: 容器设置:grid-template-rows: 1fr
,表示其内元素占满高度,其效果等同于 height : 100%
, 但在栅格布局中其成为了一个确定的值(因为在 grid 布局中,浏览器会解析出每个栅格详细的大小 ,并搭配 min-height/width,max-height/width
计算栅格是否能够放下子元素)。因此能够触发 transition
动画过渡。
总结
transition
失效的原因是 height : 100%
不是一个确定的值,而 grid-template-rows: 1fr;
却是一个确定的值,因此它可以帮我们实现动画过渡。
但要如何使用 grid-template-rows
设置栅格高度为0呢? grid-template-rows: 0fr
搭配 min-height : 0
可以帮上我们的忙。