CSS动画-auto属性的过渡

CSS动画-auto属性的过渡

通常,你想要通过css transition来过渡属性,然后使用了下面代码transition: height 0.5s linear

然后又使用了 height: auto来自适应高度height: 0 -> height: auto

然后发现动画就像没发生一样,直接就在0和auto两个状态之间直接切换

你可能会认为出现了什么bug,或者代码写错了,其实不然

你可能发生下图所示的情况

auto-problem

在这篇文章中,都使用高度进行说明,这张图里的宽度与高度是一个道理

这个问题目前还不能简单通过css完全解决

下面是我分享的4个解决方案

本文章部分资料以及Code Pen代码引用于 css-tricks.com, 里面有很多关于css的技巧,值得学习

这是浏览器bug?

查阅Mozilla Developer Network docs, auto 属性值被排除在transition规范之外。 这样做的原因是因为,如果transition一个元素的高度到auto,浏览器将会进行页面重排(reflow),重排页面会计算其他元素的位置,并且在每一个动画帧都会进行这样的操作,这意味着将花费巨大的开销。

虽然设置height: auto达到不了我们想要的效果,但是可以通过其他方法实现,下面介绍4种方式

  1. 使用max-height
  2. 使用transform: scaleY()
  3. 使用Javascript
  4. 使用Flex容器

解决方案 1: 使用 max-height

这个方法可能是最容易搜索到的方法,但是不太理想,不过在一些情况下,还是值得使用

具体方法大概如下:

  • ​ 设置 transition: max-height 0.3s ease-out
  • ​ max-height: 0
  • ​ 过渡到一个该容器能达到的最大值,比如 1000px
  • ​ max-height: 1000px

这里的max-height必须保证大于容器auto的height,不然就会出现内容显示不全的情况

GIF

Code Pen演示

在以下的code pen演示中都使用的SCSS,如果需查看CSS,请点击下面的View Compiled按钮

主要注意.section这部分

1
2
3
4
5
6
7
8
9
.section {
overflow:hidden;
transition:max-height 0.3s ease-out; /* 设置max-height的transition属性 */
height:auto;
max-height:600px; /* 这里仍然需要硬编码 */
}
.section.collapsed {
max-height:0;
}

缺点

可能你也发现了,这里的max-height也是硬编码,大多数情况下也不好确定容器的最大内容高度。

其次,设置的transition的时间和变换函数和实际展示出来的并不一致

打个比方

  • ​ 内容的实际最高宽度为100px
  • ​ 你设置了:
    • ​ max-height: 0 -> max-height: 1000px
    • ​ transition: max-height 10s linear;
  • ​ 那么这里的10s的transition显示在浏览器上就只有1s,因为1s后max-height: 100px已经达到容器最大高度,后面的max-height的变化不会体现出来

解决方案 2: transform: scaleY()

该方法不会触发页面重排(reflow)

设置方法很简单

设置 transform: scaleY(0) 到 transform: scaleY(1) 即可完成高度的缩放

该方法不会触发重排,所以元素位置不会改变,同时元素的内容会产生挤压的形变效果

GIF

Code Pen 演示

1
2
3
4
5
6
7
8
9
10
.section {
overflow:hidden;
transition:transform 0.3s ease-out; /* 设置transform的transition属性 */
height:auto;
transform:scaleY(1); /* 可以不用显示声明,但最好的做法是在这里声明一下 */
transform-origin:top; /* 设置transform的起始点,不然就会从中间开始缩放 */
}
.section.collapsed {
transform:scaleY(0); /* 设置为0 */
}

缺点

该方法普遍不适用

产生的形变效果大部分时候不是我们想要的,也不够没关

不会产生页面重排,折叠后的位置留空,大部分时候不是我们想要的情况

解决方案 3: Javascript(推荐)

该方法使用js获取到内容的最大高度,然后就能使用固定的高度进行transition了

基本思路是使用,获取到元素的高度

1
element.scrollHeight

然后设置

1
element.style.height = scrollHeight

点击后设置

1
element.style.height = 0

这样就能实现css的transition效果,并且移除了硬编码,更加灵活,适用于任意高度的容器

GIF

Code Pen 演示

要点在这两部分

1
2
var sectionHeight = element.scrollHeight; // 获取到内容的高度
element.style.height = sectionHeight + 'px'; // 设置Height

关于scrollHeight

Element.scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。

我这里放个图大家就知道了

scrollHeight

这里使用到了requestAnimationFrame(),关于这个我会在之后的博文讲解。

下面是我写的另外一种做法,比这个要直观一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
document.querySelectorAll('.dropdown-nav__title').forEach((item, index) => {
item.addEventListener(
'click',
(function(event) {
// 利用闭包存储状态
let opened = false
return function(event) {
let scrollHeight = event.target.nextElementSibling.scrollHeight
requestAnimationFrame(() => {
// 切换height
event.target.nextElementSibling.style.height = opened
? '0'
: `${scrollHeight}px`
opened = !opened
})
}
})() //立即执行函数,返回内部的function
)
})

缺点

同样的,这里没有避免掉页面重排的问题

不过在某些特定的情况下,可以通过使用绝对定位来避免页面重排,比如导航栏的下拉菜单如果需要设置动画,就不需要重排页面。

解决方案 4: FlexBox (额外方式)

之所以称之为额外方式,是因为从技术上来说该方式没有达到预期的效果,不过却是另外一种不错的方法

如果你还不够了解flexbox 和 flex-grow两个属性,推荐阅读

阮一峰的Flex 布局教程

A Complete Guide to FlexBox (英文)

这里的话主要就是用到的flex这个属性了(实质上为flex-grow)

GIF

Code Pen 演示

1
2
3
4
5
6
7
8
9
.section {
overflow:hidden; /* 注意这里是关键 */
transition:flex 0.3s ease-out; /* 设置flex的transition */
height:auto;
flex:1; /* 设置flex为1,或者1以上,即容器能够自动伸长,关于这个值的作用请见flexbox的教程 */
}
.section.collapsed {
flex: 0; /* 设置flex为0,即容器不会自动伸长 */
}

请注意上面的

1
overflow: hidden;

这句话是关键,可替换为

1
min-height: 0;

因为默认情况下,元素不会缩短至小于内容框尺寸,若想改变这一状况,请设置元素的min-widthmin-height属性。同样的设置overflow: hidden也可达到相同效果

这又是另一种效果,所以说是一种额外的解决方法。

不过这种方法利用了flexbox,更加灵活

总结

这四种方式各有优缺点,没有优劣,不过个人还是比较喜欢用js的方式,不过至于到底该用哪个,根据你的需求和情况而定。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×