如上图的折叠面板功能你是否有去实现过呢?如果有那你的实现方式是什么样的?遇到了什么问题?
如果没有那就看看我的实现方式吧
1、最大的难题
这种折叠效果实际控制的是元素的高度,css、js控制元素高度的属性有height、max-height。
既然是通过height来实现,那很简单,我直接通过样式来解决,如下:
.collapse-transition-enter-from,
.collapse-transition-leave-to{
height: 0;
}
.collapse-transition-enter-active,
.collapse-transition-leave-active{
overflow: hidden;
transition: height .3s ease-in-out, padding .3s ease-in-out, max-height .3s ease-in-out;
}
.collapse-transition-enter-to,
.collapse-transition-leave-from{
height: auto;
}
自信的写完上述代码并运行后你会发现,这并任何没有过渡效果,并且元素在隐藏的时候还有点卡顿的感觉
这是为什么呢? 原因是:transition对height: auto无效,height必须设置除auto外的有效值才会生效
那么问题来了,该什么时候获取并设置元素的高度?什么时候该恢复元素的高度?
答案是:
- 1、进入时,过渡效果进入后获取并设置元素的高度,进入过渡效果完成后立即恢复
- 2、离开时,过渡效果离开前获取并设置元素的高度,离开过渡效果完成后立即恢复
这里涉及到了4个时机,因此光靠css是完成不了的,还需依靠<transition>
组件的钩子
2、完整代码
<template>
<transition
:name="transitionName"
@before-enter="collapseBeforeEnter"
@enter="collapseEnter"
@after-enter="collapseAfterEnter"
@before-leave="collapseBeforeLeave"
@leave="collapseLeave"
@after-leave="collapseAfterLeave">
<slot></slot>
</transition>
</template>
<script>
/**
* 元素折叠过度效果
*/
export default {
name: 'CollapseTransition',
props: {
transitionName: {
type: String,
default: 'collapse-transition'
}
},
data () {
return {
oldPaddingTop: '',
oldPaddingBottom: '',
oldOverflow: ''
}
},
methods: {
collapseBeforeEnter (el) {
// console.log('11, collapseBeforeEnter');
this.oldPaddingBottom = el.style.paddingBottom;
this.oldPaddingTop = el.style.paddingTop;
// 过渡效果开始前设置元素的maxHeight为0,让元素maxHeight有一个初始值
el.style.paddingTop = '0';
el.style.paddingBottom = '0';
el.style.maxHeight = '0';
},
collapseEnter (el, done) {
// console.log('22, collapseEnter');
//
this.oldOverflow = el.style.overflow;
let elHeight = el.scrollHeight;
// 过渡效果进入后将元素的maxHeight设置为元素本身的高度,将元素maxHeight设置为auto不会有过渡效果
if (elHeight > 0) {
el.style.maxHeight = elHeight + 'px';
} else {
el.style.maxHeight = '0';
}
el.style.paddingTop = this.oldPaddingTop;
el.style.paddingBottom = this.oldPaddingBottom;
el.style.overflow = 'hidden';
// done();
let onTransitionDone = function () {
done();
// console.log('enter onTransitionDone');
el.removeEventListener('transitionend', onTransitionDone, false);
el.removeEventListener('transitioncancel', onTransitionDone, false);
};
// 绑定元素的transition完成事件,在transition完成后立即完成vue的过度动效
el.addEventListener('transitionend', onTransitionDone, false);
el.addEventListener('transitioncancel', onTransitionDone, false);
},
collapseAfterEnter (el) {
// console.log('33, collapseAfterEnter');
// 过渡效果完成后恢复元素的maxHeight
el.style.maxHeight = '';
el.style.overflow = this.oldOverflow;
},
collapseBeforeLeave (el) {
// console.log('44, collapseBeforeLeave', el.scrollHeight);
this.oldPaddingBottom = el.style.paddingBottom;
this.oldPaddingTop = el.style.paddingTop;
this.oldOverflow = el.style.overflow;
el.style.maxHeight = el.scrollHeight + 'px';
el.style.overflow = 'hidden';
},
collapseLeave (el, done) {
// console.log('55, collapseLeave', el.scrollHeight);
if (el.scrollHeight !== 0) {
el.style.maxHeight = '0';
el.style.paddingBottom = '0';
el.style.paddingTop = '0';
}
// done();
let onTransitionDone = function () {
done();
// console.log('leave onTransitionDone');
el.removeEventListener('transitionend', onTransitionDone, false);
el.removeEventListener('transitioncancel', onTransitionDone, false);
};
// 绑定元素的transition完成事件,在transition完成后立即完成vue的过度动效
el.addEventListener('transitionend', onTransitionDone, false);
el.addEventListener('transitioncancel', onTransitionDone, false);
},
collapseAfterLeave (el) {
// console.log('66, collapseAfterLeave');
el.style.maxHeight = '';
el.style.overflow = this.oldOverflow;
el.style.paddingBottom = this.oldPaddingBottom;
el.style.paddingTop = this.oldPaddingTop;
this.oldOverflow = this.oldPaddingBottom = this.oldPaddingTop = '';
}
}
};
</script>
<style lang="less">
.collapse-transition-enter-active,
.collapse-transition-leave-active{
transition: height .3s ease-in-out, padding .3s ease-in-out, max-height .3s ease-in-out;
}
</style>
看完代码后你可能会奇怪为什么是设置元素的max-height
而不是元素的height
?
这么做的原因是:不管元素本身有没有通过style
设置height
,都不会影响元素高度的计算
3、使用组件
<template>
<div class="home">
<div style="padding: 50px;">
<div style="margin-bottom: 10px;">
<button type="button" @click="toggleExpand">展开/折叠</button>
</div>
<CollapseTransition>
<div class="description" v-show="expanded">
<dl>
<dt>操作账号:</dt><dd>super_admin</dd>
</dl>
<dl>
<dt>操作模块:</dt><dd>日志模块</dd>
</dl>
<dl>
<dt>操作描述:</dt><dd>查看系统日志</dd>
</dl>
<dl>
<dt>操作IP:</dt><dd>192.168.1.100</dd>
</dl>
</div>
</CollapseTransition>
</div>
</div>
</template>
<script>
import CollapseTransition from './CollapseTransition'
export default {
name: 'home',
components: {
CollapseTransition
},
data() {
return {
expanded: false
}
},
methods: {
toggleExpand () {
this.expanded = !this.expanded;
}
}
}
</script>
<style type="text/css" lang="less" scoped>
.description{
padding: 15px;
width: 600px;
border: 1px solid #3a8ee6;
dl{ margin-bottom: 5px; }
dt{ display: block; float: left; }
dd{ display: block; overflow: hidden; }
}
</style>
最终效果:
4、最后
有了这个组件后想实现折叠面板组件
、菜单组件
、树组件
等就轻而易举了!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。