flip动画思想 & 获取元素的布局属性一定会引起页面重排

194 阅读3分钟

一、概念

flip动画分为4个阶段:

  1. first(记录要监控的元素位置)
  2. last(记录元素结构变化的位置)
  3. inver(移动元素到first的位置-“归位”)
  4. play(播放动画)

flip就是4个阶段的首字母,它的特点是动画是在元素变动完成后,再开始播放动画,此时的元素已经是位于终点了,所以播放的过渡动画是“归位”的过渡动画

二、例子

过渡动画:鼠标移入按钮时,wrapper高度为auto,移出按钮时,高度设置为0

动画.gif

想要使用css实现这种动画,有几种方式:

  1. 由于过渡动画要求动画属性值必须是数值,height的属性为auto和0时,无法看到动画效果,那么设置一个max-height。但是max-height为了一定要显示完整的wrapper内容,往往需要设置地很大,这样动画就会有一定的加快和延迟,以为在单位时间内,假使动画是匀速的,那么在移入时动画会非常快,而在移出时,动画会有一定的延时
  2. 使用缩放属性,缺点在于会压缩内容,如果产品和ui觉得可以,这个方式也可
  3. 使用网格布局,grid-template-rows的值是数值类型的,过渡动画没问题,但是在safari浏览器不支持

使用js来实现:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .wrapper {
        width: 200px;
        background: deeppink;
        height: 0;
        overflow: hidden;
        transition: 0.3s;
      }
    </style>
  </head>
  <body>
    <button>按钮</button>
    <div class="wrapper">
      <p>文字</p>
      <p>文字</p>
      <p>文字</p>
      <p>文字</p>
      <p>文字</p>
      <p>文字</p>
      <p>文字</p>
      <p>文字</p>
    </div>
    <script>
      const btn = document.querySelector('button')
      const wrapper = document.querySelector('.wrapper')

      // flip思想:先让元素到达最终状态,获取到最终状态的height,再让元素回到初始状态,再播放动画,从初始状态变更为最终状态
      btn.onmouseenter = function () {
        wrapper.style.height = 'auto' // 先设置为auto,目的是为了拿到元素实际的高度
        const { height } = wrapper.getBoundingClientRect()
        wrapper.style.height = 0 // 再设置为0,隐藏wrapper元素。这样页面会不会闪一下?不会,在执行js的过程中,浏览器是来不绘制的
        wrapper.offsetHeight // 触发页面重排,强制渲染height为0
        wrapper.style.height = height + 'px'
      }

      btn.onmouseleave = function () {
        wrapper.style.height = 0
      }
    </script>
  </body>
</html>

问题1:为什么在设置height为0和设置height为height + 'px'中间读取offsetHeight?

这涉及到浏览器的重排原理,当获取元素布局属性时,会立即重排。我读取offsetHeight的目的是必须要让浏览器先展示height为0的样式,然后再设置height为实际高度,此时transition的过渡时间才会生效

问题2:为什么获取布局属性时会引起重排?

当你访问元素的布局属性时,浏览器必须要给你一个正确的属性值,例如访问offsetHeight,浏览器会立即暂停并应用所有样式和布局改动,返回准确的offsetHeight值

这里如果获取的是btn的offsetHeight属性也是一样的会引起重排,或者是获取其他布局属性(scrollWidth)都是会一样地引起重排

如果你不知道获取元素布局属性会导致页面重排,那么使用定时器也是可以实现一样的效果:

        setTimeout(() => {
          wrapper.style.height = height + 'px'
        }, 100)

如果你觉得这篇文章对你有用,可以看看作者封装的库xtt-utils,里面封装了非常实用的js方法。如果你也是vue开发者,那更好了,除了常用的api,还有大量的基于element-ui组件库二次封装的使用方法和自定义指令等,帮你提升开发效率。不定期更新,欢迎交流~