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

相关推荐
雪落满地香16 分钟前
前端:改变鼠标点击物体的颜色
前端
余生H1 小时前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
outstanding木槿1 小时前
JS中for循环里的ajax请求不数据
前端·javascript·react.js·ajax
酥饼~1 小时前
html固定头和第一列简单例子
前端·javascript·html
一只不会编程的猫1 小时前
高德地图自定义折线矢量图形
前端·vue.js·vue
m0_748250931 小时前
html 通用错误页面
前端·html
来吧~1 小时前
vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)
前端·vue.js·音视频
han_1 小时前
不是哥们,我的console.log突然打印不出东西了!
前端·javascript·chrome
魔术师卡颂1 小时前
最近看到太多 cursor 带来的焦虑,有些话想说
前端·aigc·openai
鎈卟誃筅甡1 小时前
Vuex 的使用和原理详解
前端·javascript