最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • [element-ui源码]element-ui的collapse-transition到底写了什么?

    正文概述 掘金(村上小树)   2020-12-10   1168

    1.回顾涉及到的基础

    [Vue深入浅出]知晓Vue中的render函数

    2.element-ui中的collapse-transition.js

    源码:

    import { addClass, removeClass } from 'element-ui/src/utils/dom';
    
    class Transition {
      beforeEnter(el) {
        addClass(el, 'collapse-transition');
        if (!el.dataset) el.dataset = {};
    
        el.dataset.oldPaddingTop = el.style.paddingTop;
        el.dataset.oldPaddingBottom = el.style.paddingBottom;
    
        el.style.height = '0';
        el.style.paddingTop = 0;
        el.style.paddingBottom = 0;
      }
    
      enter(el) {
        el.dataset.oldOverflow = el.style.overflow;
        if (el.scrollHeight !== 0) {
          el.style.height = el.scrollHeight + 'px';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        } else {
          el.style.height = '';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        }
    
        el.style.overflow = 'hidden';
      }
    
      afterEnter(el) {
        // for safari: remove class then reset height is necessary
        removeClass(el, 'collapse-transition');
        el.style.height = '';
        el.style.overflow = el.dataset.oldOverflow;
      }
    
      beforeLeave(el) {
        if (!el.dataset) el.dataset = {};
        el.dataset.oldPaddingTop = el.style.paddingTop;
        el.dataset.oldPaddingBottom = el.style.paddingBottom;
        el.dataset.oldOverflow = el.style.overflow;
    
        el.style.height = el.scrollHeight + 'px';
        el.style.overflow = 'hidden';
      }
    
      leave(el) {
        if (el.scrollHeight !== 0) {
          // for safari: add class after set height, or it will jump to zero height suddenly, weired
          addClass(el, 'collapse-transition');
          el.style.height = 0;
          el.style.paddingTop = 0;
          el.style.paddingBottom = 0;
        }
      }
    
      afterLeave(el) {
        removeClass(el, 'collapse-transition');
        el.style.height = '';
        el.style.overflow = el.dataset.oldOverflow;
        el.style.paddingTop = el.dataset.oldPaddingTop;
        el.style.paddingBottom = el.dataset.oldPaddingBottom;
      }
    }
    
    export default {
      name: 'ElCollapseTransition',
      functional: true,
      render(h, { children }) {
        const data = {
          on: new Transition()
        };
    
        return h('transition', data, children);
      }
    };
    

    场景:

    在el-tree,el-menu,el-collapse中都有用过上述这个组件。例如:

    el-tree:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    el-menu:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    el-collapse:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    解释:

    先从源码最后的export default那部分代码进行分析。从name: 'ElCollapseTransition'functional: true可以知道这是一个函数式组件。

    然后分析render函数,render传入的h就是createElement函数,children是从context(就是vnode.context,也就是实例中的this)中解构出来的,相当于已被实例化的components中注册的组件。

    然后h的传入参数,第一个参数是要生成的标签或者组件的名称,这里传入transition也就是要生成transition组件。第二个参数为数据对象,这里实例化源码上部分声明的Transition类,类里面定义好过渡动画时的钩子函数(beforeEnterenterafterEnterbeforeLeaveleaveafterLeave)。后面把这个Transition类的实例放在on事件监听属性里,效果就跟下面的函数一样:

    <transition
      v-on:before-enter="beforeEnter"
      v-on:enter="enter"
      v-on:after-enter="afterEnter"
    
      v-on:before-leave="beforeLeave"
      v-on:leave="leave"
      v-on:after-leave="afterLeave"
    >
      <!-- ... -->
    </transition>
    

    接下来再看钩子函数里的内容,先看beforeEnterenterafterEnter。执行顺序依次为beforeEnter>enter>afterEnter

     beforeEnter(el) {
        addClass(el, 'collapse-transition');
        if (!el.dataset) el.dataset = {};
    
        el.dataset.oldPaddingTop = el.style.paddingTop;
        el.dataset.oldPaddingBottom = el.style.paddingBottom;
    
        el.style.height = '0';
        el.style.paddingTop = 0;
        el.style.paddingBottom = 0;
      }
    
      enter(el) {
        el.dataset.oldOverflow = el.style.overflow;
        if (el.scrollHeight !== 0) {
          el.style.height = el.scrollHeight + 'px';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        } else {
          el.style.height = '';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        }
    
        el.style.overflow = 'hidden';
      }
    
      afterEnter(el) {
        // for safari: remove class then reset height is necessary
        removeClass(el, 'collapse-transition');
        el.style.height = '';
        el.style.overflow = el.dataset.oldOverflow;
      }
    

    beforeEnter中,一开始添加collapse-transition的class。类的定义如下:

    // packages\theme-chalk\src\common\transition.scss
    .collapse-transition {
      transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out;
    }
    

    .collapse-transition可知,整个ElCollapseTransition的展示原理就是让**height****padding-top****padding-bottom**从0到设定值的过程,加上**transition**动画过渡。

    回到beforeEnter函数中继续分析,先初始化dataset用于存放html节点的data-*属性。然后把style里的paddingToppaddingBottom的数据暂存到dataset中,然后把heightpaddingToppaddingBottom都设置为0。

    enter函数中,通过el.scrollHeight获取元素的正文全文高度,然后赋值到el.style.height中。如果el.scrollHeight为0,则代表el没有子元素,代表其高度不是靠子元素撑起来的,可能是在css中定义好height的,此时通过el.style.height = '',让元素高度计算取值恢复到css的class中定义的高度。

    然后通过el.style.paddingTop = el.dataset.oldPaddingTop; el.style.paddingBottom = el.dataset.oldPaddingBottom;恢复paddingToppaddingBottom初始值。

    最后afterEnter函数中,移除collapse-transition的class,然后el.style.height = ''以恢复设定值。

    之后的 beforeLeaveleaveafterLeave同理。

    拓展:

    clientHeight,offsetHeight,scrollHeight的区别:

    clientHeight:包括padding但不包括border、margin的元素的页面可视高度,有滚动条时计算统计滚动条高度。对于inline的元素这个属性一直是0,单位px,只读。如下图所示:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    offsetHeight:和clientHeight的区别是统计高度时还包括border。如下图所示:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    scrollHeight:和clientHeight的区别是,无论有没有滚动条,统计的都是元素正文高度,如下图所示:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    3.collapse-transition存在的问题

    举一个场景,先放出代码:

    <template lang="pug">
      .content
        el-button(@click="visible=!visible") {{visible?'显示':'隐藏'}}
        el-collapse-transition
          .box1(v-if="visible")
            .box2
    </template>
    <script>
    export default {
      data () {
        return {
          visible: false
        }
      }
    }
    </script>
    <style lang="scss" scoped>
      .box1 {
        position: relative;
        width: 200px;
        height: 200px;
        overflow: auto;
      }
    
      .box2 {
        width: 100%;
        height: 400px;
        background-color: red;
      }
    </style>
    

    el-collapse-transition里面存在一个box1,box1里面也存在一个box2。box1的高度为200px,比box2的高度400px要小。在动画效果如下所示:

    [element-ui源码]element-ui的collapse-transition到底写了什么?

    总结特征:

    • 展开时,先顺畅展开到box2的高度,动画结束时变成box1的高度
    • 隐藏式,高度先突然从box1变回box2的高度,后再顺场地变为0

    原因分析:

    这里直接贴涉及到的代码和注释:

      enter(el) {
        el.dataset.oldOverflow = el.style.overflow;
        if (el.scrollHeight !== 0) {
          // enter状态时,el.style.height直接取scrollHeight的高度,即box2的高度400px
          el.style.height = el.scrollHeight + 'px';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        } else {
          el.style.height = '';
          el.style.paddingTop = el.dataset.oldPaddingTop;
          el.style.paddingBottom = el.dataset.oldPaddingBottom;
        }
    
        el.style.overflow = 'hidden';
      }
    
      afterEnter(el) {
        removeClass(el, 'collapse-transition');
        // afterEnter状态时,el.style.height设置为'',此时高度变回el在.box1类中设置的高度200px
        el.style.height = '';
        el.style.overflow = el.dataset.oldOverflow;
      }
    
      beforeLeave(el) {
        if (!el.dataset) el.dataset = {};
        el.dataset.oldPaddingTop = el.style.paddingTop;
        el.dataset.oldPaddingBottom = el.style.paddingBottom;
        el.dataset.oldOverflow = el.style.overflow;
        // el.style.height直接取scrollHeight的高度,即box2的高度400px
        el.style.height = el.scrollHeight + 'px';
        el.style.overflow = 'hidden';
      }
    
      leave(el) {
        if (el.scrollHeight !== 0) {
          // for safari: add class after set height, or it will jump to zero height suddenly, weired
          addClass(el, 'collapse-transition');
          el.style.height = 0;
          el.style.paddingTop = 0;
          el.style.paddingBottom = 0;
        }
      }
    

    如何避免这类情况:

    在原代码上进行修改:

    <template lang="pug">
      .ts-yard
        el-button(@click="visible=!visible") {{visible?'隐藏':'显示'}}
        el-collapse-transition
          // 用一个普通的div包裹着.box1,且把v-if判断条件移到该div上,其他代码不变
          .box(v-if="visible")
            .box1
              .box2
    </template>
    <script>
       // 和之前一样...
    </script>
    <style lang="scss" scoped>
      // 和之前一样...
    </style>
    

    用一个div包裹要展示的内容主要是为了让el.scrollHeight得出的高度和el自身高度一样。

    最后显示更改后的效果:

    [element-ui源码]element-ui的collapse-transition到底写了什么?


    起源地下载网 » [element-ui源码]element-ui的collapse-transition到底写了什么?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元