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:
el-menu:
el-collapse:
解释:
先从源码最后的export default
那部分代码进行分析。从name: 'ElCollapseTransition'
和functional: true
可以知道这是一个函数式组件。
然后分析render
函数,render
传入的h
就是createElement
函数,children
是从context(就是vnode.context,也就是实例中的this)中解构出来的,相当于已被实例化的components中注册的组件。
然后h
的传入参数,第一个参数是要生成的标签或者组件的名称,这里传入transition
也就是要生成transition组件。第二个参数为数据对象,这里实例化源码上部分声明的Transition类,类里面定义好过渡动画时的钩子函数(beforeEnter
,enter
,afterEnter
,beforeLeave
,leave
,afterLeave
)。后面把这个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>
接下来再看钩子函数里的内容,先看beforeEnter
,enter
,afterEnter
。执行顺序依次为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
里的paddingTop
和paddingBottom
的数据暂存到dataset
中,然后把height
,paddingTop
,paddingBottom
都设置为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;
恢复paddingTop
和paddingBottom
初始值。
最后afterEnter
函数中,移除collapse-transition
的class,然后el.style.height = ''
以恢复设定值。
之后的 beforeLeave
,leave
,afterLeave
同理。
拓展:
clientHeight,offsetHeight,scrollHeight的区别:
clientHeight:包括padding但不包括border、margin的元素的页面可视高度,有滚动条时计算统计滚动条高度。对于inline的元素这个属性一直是0,单位px,只读。如下图所示:
offsetHeight:和clientHeight的区别是统计高度时还包括border。如下图所示:
scrollHeight:和clientHeight的区别是,无论有没有滚动条,统计的都是元素正文高度,如下图所示:
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要小。在动画效果如下所示:
总结特征:
- 展开时,先顺畅展开到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自身高度一样。
最后显示更改后的效果:
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!