深圳幻海软件技术有限公司 欢迎您!

CSS 实现超过固定高度后出现展开折叠按钮

2023-02-28

在平时开发中,经常会碰到一些需要判断高度的场景,比如当超过一定高度后,需要自动出现展开折叠按钮,如下:传统的思路肯定是通过JS去动态计算容器的高度,但这样就涉及到加载时机的问题,获取早了可能元素还没渲染好,晚了又会有明显的卡顿感,或者会引起页面的闪烁。那有没有仅通过CSS的方法呢?当然也是有的!要实

在平时开发中,经常会碰到一些需要判断高度的场景,比如当超过一定高度后,需要自动出现展开折叠按钮,如下:

传统的思路肯定是通过JS去动态计算容器的高度,但这样就涉及到加载时机的问题,获取早了可能元素还没渲染好,晚了又会有明显的卡顿感,或者会引起页面的闪烁。

那有没有仅通过CSS的方法呢?

当然也是有的!要实现上面这个例子的效果,需要解决以下几个问题:

  1. 如何判断不同的高度?
  2. 如何在不同的高度下展示隐藏点击按钮?
  3. 如何点击切换?

花几分钟一起看看吧😁

一、先思考一下布局

明确来讲,CSS现在已经有相关方法可以判断高度了,那就是CSS容器查询[1]。不过这个特性太高级了,目前几乎还不能实战,我们这次介绍一种更加传统的方式。

如何判断不同的高度?换句话来说,什么样的布局在不同的高度下会有截然不同的效果?

思考一下:

绝对定位?位置完全固定了,不行。

flex布局?好像也只能控制水平方向上

grid布局?这个水太深,没来得及研究(可能也行?)

等等,除了以上,还有一个现在都避而不谈的浮动布局,为啥现在都很少用了呢,原因在于浮动布局非常脆弱,细小的尺寸变化都能引起整个布局的坍塌。我记得以前用浮动布局的时候,都需要尺寸精确,稍微出一点差错就导致浮动元素不知道跑哪去了...

既然对尺寸非常敏感,是不是和本文的临界高度有一定联系呢?

没错,今天要用到的方式就是浮动布局。

二、浮动布局的奥妙

一步一步,来搭建我们所需要的页面雏形。

我们先来看一个有趣的现象,这里有一个容器,里面有3个子节点,分别为A、B、C,其中A左浮动,B、C右浮动。

<div class="box">
 <div class="a">A</div>
 <div class="b">B</div>
 <div class="c">C</div>
</div>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
.a{
 width: 100px;
 height: 100px;
 float: left;
 background-color: cadetblue;
}
.b{
 width: 300px;
 height: 100px;
 float: right;
 background-color: coral;
}
.c{
 width: 50px;
 height: 100px;
 float: right;
 background-color: darkgreen;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

当横向空间足够时,效果是这样的。

此时,A贴近左边,B贴近右边,C贴着B。

如果横向空间不足,那么C就会换行。

现在C看似好像跑到了B的下方,其实是因为B的高度还没有超过A。

当B的高度超过A时,那么C会有如下表现。

此时,C贴在B的左侧,A的下方。

是不是很神奇?除了浮动布局,没有什么方法可以实现这样的效果了吧。😲那么,这和本文的例子有什么关系呢?

别急,其实这是一种极端情况,接着往下看。

三、极端情况下的浮动表现

我们可以设置一个极限状态,比如A的高度充满容器,B的宽度充满容器,此时B肯定会掉下来,我们用负的margin让B仍保持在一行,如下:

.a{
  width: 50px;
  height: 100%;
}
.b{
  width;100%;
  margin-left: -50px;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

此时,B的高度不高于A,所以C仍然是贴在B的下方,并且靠右。现在让B的高度超过A,也就是超出容器高度,就变成了这样。

此时,C位于A的下方。也就是,仅仅因为高度超过了一个临界值,C就得到了两种截然不同的位置,如下:

下面是动态演示(动态改变B的高度)。

试想一下,把C当做是“展开折叠”按钮,在这个基础上挪动一下C的相对位置,移到正下方,是不是就是我们需要的效果了呢?下面的虚线框示意移动后的位置,这样在视区范围内,虚线框在高度不足时就是不可见的,只有在超过固定高度后才可见,示意如下(观察虚线框的位置)。

完整 demo 可以查看以下任意链接

  • float demo (juejin.cn)[2]
  • float demo (codepen.io)[3]
  • float demo (runjs.work)[4]

整个原理就是这样了,下面来看具体实现。

四、CSS 具体实现

现在回到最开头的例子,根据前面的 demo 原型,可以改造成以下结构。

<div class="content">
  <pre class="text">
        很多内容...
        很多内容...
  </pre>
  <label for="c1" class="btn"></label>
</div>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

这里.text就相当于B,.btn就相当于C,至于A完全可以用伪元素::before来代替。

由于有点击切换的交互,所以需要用到input checkbox,和label关联起来,所以结构最终改造成这样。

<div class="section">
  <input class="content-check" type="checkbox" id="c1" hidden>
  <div class="content">
    <pre class="text">
        很多内容...
        很多内容...
        </pre>
    <label for="c1" class="btn"></label>
  </div>
</div>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

然后加点样式美化一下吧,由于原理和前面是完全一致的,这里就不重复展示具体细节了。

.content{
  width: 400px;
  max-height: 200px;
  overflow: hidden;
  border-radius: 4px;
  outline: 2px dashed royalblue;
}
.section{
  display: flex;
}
pre{
  white-space: pre-wrap;
}
.content::before{
  content: '';
  width: 100px;
  height: 100%;
  float: left;
}
.btn{
  float: right;
  width: 100px;
  text-align: center;
  position: relative;
  left: calc(50% - 50px);
  transform: translateY(-100%);
  cursor: pointer;
}
.btn::after{
  content: '';
  display: block;
  height: 34px;
  background-color: #666;
  transition: .2s background-color;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E %3Cpath d='M143 352.3L7 216.3c-9.4-9.4-9.4-24.6 0-33.9l22.6-22.6c9.4-9.4 24.6-9.4 33.9 0l96.4 96.4 96.4-96.4c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9l-136 136c-9.2 9.4-24.4 9.4-33.8 0z'%3E%3C/path%3E %3C/svg%3E") center/ 24px 24px no-repeat;
}
.btn:hover::after{
  background-color: royalblue;
}
.btn::before{
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 34px;
}
.text{
  box-sizing: border-box;
  width: 100%;
  padding: 10px 15px;
  float: right;
  line-height: 1.5;
  margin: 0;
  margin-left: -100px;
  font-size: 18px;
  color: #232323;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.

效果如下:

然后是点击切换效果,可以用:checked来控制。

.content-check:checked+.content{
  max-height: fit-content;
}
.content-check:checked+.content .btn{
  left: auto;
  right: calc(50% - 50px);
}
.content-check:checked+.content .btn::after{
  transform: scaleY(-1);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

这样就可以控制不同的状态了。

还可以加点遮罩,让点击处有一种淡出弱化的效果,表示下面还有内容。

.text{
  /* */
  -webkit-mask: linear-gradient(red 150px, transparent 200px);
}
  • 1.
  • 2.
  • 3.
  • 4.

效果如下:

完整 demo 可以访问以下任意链接

  • CSS auto height expansion (juejin.cn)[5]
  • CSS auto height expansion (codepen.io)[6]
  • CSS auto height expansion (runjs.work)[7]

五、最后总结一下

想不到浮动布局还能实现这样的功能,总的来说,这是一种成本低廉但需要点想象力的实现方式,适应性和兼容性也都不错,下面总结一下:

  1. 布局有很多种,浮动布局比较特殊。
  2. 浮动布局非常脆弱,细小的尺寸都能引起整个布局的坍塌。
  3. 超过指定高度后,由于浮动布局引起的坍塌,正好可以区分两种情况。
  4. 通过改变按钮本身的相对位置,可以让案例在超出指定高度后才可见。
  5. 点击切换可以用input:check和label相关联实现。
  6. 淡出弱化效果可以添加一层蒙版mask,表示下面还有内容。

参考资料

[1]CSS容器查询: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries。

[2]float demo (juejin.cn): https://code.juejin.cn/pen/7201418511582199842。

[3]float demo (codepen.io): https://codepen.io/xboxyan/pen/zYJvxaN。

[4]float demo (runjs.work): ​​https://runjs.work/projects/b7e36f3a248144cd。​​

[5]CSS auto height expansion (juejin.cn): https://code.juejin.cn/pen/7201418292035059764。

[6]CSS auto height expansion (codepen.io): https://codepen.io/xboxyan/pen/xxawbWd。

[7]CSS auto height expansion (runjs.work): https://runjs.work/projects/5aa0d64cc2d74d09。