简单且实用的CSS小技巧——手风琴动画(一)

如何实现手风琴动画?

如果让你用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, frmin-height/width ,以下是较为直观的解释:

  • 1fr 表示占据主轴中尽量多的空间
  • 0fr 表示占据主轴中尽量少的空间 。这个空间可看作 min-height/widthmin-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 可以帮上我们的忙。

相关推荐
亦世凡华、12 分钟前
Rollup入门与进阶:为现代Web应用构建超小的打包文件
前端·经验分享·rollup·配置项目·前端分享
Bl_a_ck38 分钟前
【React】Craco 简介
开发语言·前端·react.js·typescript·前端框架
为美好的生活献上中指1 小时前
java每日精进 5.11【WebSocket】
java·javascript·css·网络·sql·websocket·网络协议
augenstern4162 小时前
webpack重构优化
前端·webpack·重构
海拥✘2 小时前
CodeBuddy终极测评:中国版Cursor的开发革命(含安装指南+HTML游戏实战)
前端·游戏·html
寧笙(Lycode)2 小时前
React系列——HOC高阶组件的封装与使用
前端·react.js·前端框架
asqq82 小时前
CSS 中的 ::before 和 ::after 伪元素
前端·css
拖孩3 小时前
【Nova UI】十五、打造组件库之滚动条组件(上):滚动条组件的起步与进阶
前端·javascript·css·vue.js·ui组件库
Hejjon3 小时前
Vue2 elementUI 二次封装命令式表单弹框组件
前端·vue.js
小堃学编程4 小时前
前端学习(3)—— CSS实现热搜榜
前端·学习