css 自动高度支持过度动画

1,007 阅读4分钟

前言

前端项目总有很多点击打开/关闭某个元素的需求,为了给用户更好的体验,我们在打开或收起一个元素的时候,希望它能有一个过渡动画,而不是直接出现或消失。而当一个元素的高度不固定时使用 transition + height: auto, 是无法实现动画效果的。以下我总结了五种实现方法,同时对比了它们的优缺点以及适用范围。

一、使用 interpolate-size

interpolate-size 是 CSS 中的一个新属性,它可以让元素尺寸相关的属性在过渡过程中进行插值计算。通过在需要过度元素的祖先元素上设置 interpolate-size: allow-keywords;,可以让元素的高度在过渡过程中进行插值计算,从而实现过渡动画。

css 代码

:root {
  interpolate-size: allow-keywords; /* 重点 加上该属性后 使用 transition + height: auto 就可以有过度动画了 */
}

.interpolate-size-container {
  height: 0px;
  overflow: hidden;
  transition: height 0.3s linear;
}

.interpolate-size-container.open {
  height: auto;
}

html 代码

<button id="toggle-button">切换</button>
<div
  id="target"
  class="interpolate-size-container"
  style="background-color: aquamarine">
  <p>interpolate-size: allow-keywords</p>
  <p>2 不固定</p>
  <p>3 未设置高度</p>
</div>

js 代码

document.getElementById('toggle-button').addEventListener('click', function () {
  const target = document.getElementById('target')
  target.classList.toggle('open')
})

实现效果

interpolate-size.gif

二、使用 calc-size

calc-size 是 CSS 中的一个新函数,他可以使得 auto、max-content、min-content 等非确定数值的值可以拥有过度效果。

css 代码

.calc-size-container {
  height: 0px;
  overflow: hidden;
  transition: height 0.3s linear;
}

.calc-size-container.open {
  height: calc-size(max-content, size);
}

html 代码

<button id="toggle-button">切换</button>
<div
  id="target"
  class="calc-size-container"
  style="background-color: aquamarine">
  <p>calc-size</p>
  <p>2 不固定</p>
  <p>3 未设置高度</p>
</div>

js 代码

document.getElementById('toggle-button').addEventListener('click', function () {
  const target = document.getElementById('target')
  target.classList.toggle('open')
})

实现效果

calc-size.gif

三、使用 grid 布局

这里是利用 grid 的 fr 单位,由于 fr 前是数值所以可以被 css 计算,从而实现过渡动画。 注意:grid 内需要有一个元素,因为 fr 是设置到子元素上的,并且这个元素的最小高度需要设置为 0,否则会被内容撑开。

css 代码

.grid-example-container {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s linear;
  overflow: hidden;
}

.grid-example-container > div {
  min-height: 0px;
}

.grid-example-container.open {
  grid-template-rows: 1fr;
}

html 代码

<button id="toggle-button">切换</button>
<div
  id="target"
  class="grid-example-container"
  style="background-color: aquamarine">
  <div>
    <p>grid</p>
    <p>2 不固定</p>
    <p>3 未设置高度</p>
  </div>
</div>

js 代码

document.getElementById('toggle-button').addEventListener('click', function () {
  const target = document.getElementById('target')
  target.classList.toggle('open')
})

实现效果

grid.gif

四、使用 max-height

设置 max-height 为固定的数值,这个数值比需要超过元素的实际高度;这里就有一个小问题,如果元素的高度完全未知,那么 max-height 的值需要设置得特别大,否则展开时将不能看到元素的全部内容;但是实际过度的是 max-height 的值,所以在过度时当 max-height 超过元素实际高度时后的时间过度动画将不可见了,由此会出现一个问题,展开时实际过度时间比设置的过度时间短,收起时会 delay 一下然后再执行过度,并且过度的速度将会比较快。

css 代码

.max-height-container {
  max-height: 0px;
  transition: max-height 0.3s linear;
  overflow: hidden;
}

.max-height-container.open {
  max-height: 400px;
}

html 代码

<button id="toggle-button">切换</button>
<div
  id="target"
  class="max-height-container"
  style="background-color: aquamarine">
  <p>max-height</p>
  <p>2 不固定</p>
  <p>3 未设置高度</p>
</div>

js 代码

document.getElementById('toggle-button').addEventListener('click', function () {
  const target = document.getElementById('target')
  target.classList.toggle('open')
})

实现效果

max-height.gif

五、使用 js 计算高度

利用 js 读取元素的实际高度,给元素设置高度的数值,即可实现动画效果;这种方式实现起来相对比较复杂,但是灵活性强

css 代码

.js-container {
  transition: height 0.3s linear;
  overflow: hidden;
}

html 代码

<button id="toggle-button">切换</button>
<div
  id="target"
  class="js-container"
  style="background-color: aquamarine">
  <p>js 计算</p>
  <p>2 不固定</p>
  <p>3 未设置高度</p>
</div>

js 代码

document.getElementById('toggle-button').addEventListener('click', function () {
  const target = document.getElementById('target')
  const { height } = target.getBoundingClientRect()

  if (height === 0) {
    const factHeight = target.scrollHeight
    target.style.height = '0px'
    target.getBoundingClientRect() // 强制css从新计算,让设置的height=0px生效
    target.style.height = `${factHeight}px`
    setTimeout(() => {
      target.style.height = '' // 动画完成后,将高度取消,以适应其高度变化
    }, 300)
  } else {
    target.style.height = `${height}px`
    target.getBoundingClientRect() // 强制css从新计算,让设置的height=${height}px生效
    target.style.height = '0px'
  }
})

实现效果

js.gif

总结

方法优点缺点使用范围
interpolate-size简单易用,css 支持的属性,性能好仅比较新的浏览器支持chrome:129+,edge:129+,opera:115+
calc-size简单易用,css 支持的属性,性能好仅比较新的浏览器支持chrome:129+,edge:129+,opera:115+
grid简单易用,兼容性相对较好需要多嵌套一层 dom无需兼容低版本浏览器
max-height简单易用,兼容所有浏览器用户体验不好,看起来像有点卡顿元素实际高相差不大,比较可控时
js 计算兼容所有浏览器,可控性好实现复杂,需要手动计算元素的实际高度,性能不好都可用,在现代的设备上这点性能问题应该问题不大

css 真的是一门永远都学不会的语言,期待将来能有更多好用的功能,并且各个浏览器都给支持上。

最后推荐一下低代码平台我的应用,可以直接去下载后私有化部署且完全免费。开源不易,望多多支持,也可通过平台提出宝贵意见,感谢!