简单且实用的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 可以帮上我们的忙。

相关推荐
excel7 分钟前
Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解
前端
前端大卫17 分钟前
一个关于时区的线上问题
前端·javascript·vue.js
whltaoin1 小时前
中秋赏月互动页面:用前端技术演绎传统节日之美
前端·javascript·html·css3·中秋主题前端
IT派同学2 小时前
TableWiz诞生记:一个被表格合并逼疯的程序员如何自救
前端·vue.js
西洼工作室4 小时前
CSS高效开发三大方向
前端·css
昔人'4 小时前
css`font-variant-numeric: tabular-nums` 用来控制数字的样式。
前端·css
铅笔侠_小龙虾4 小时前
动手实现简单Vue.js ,探索Vue原理
前端·javascript·vue.js
哟哟耶耶6 小时前
Starting again-02
开发语言·前端·javascript
Apifox.6 小时前
Apifox 9 月更新| AI 生成接口测试用例、在线文档调试能力全面升级、内置更多 HTTP 状态码、支持将目录转换为模块
前端·人工智能·后端·http·ai·测试用例·postman
Kitasan Burakku6 小时前
Typescript return type
前端·javascript·typescript