在许多应用中,我们可能需要实现展开和收缩的动画效果。在本文中,我们将探讨如何在 Vue 中实现这种效果。
实现该功能分为下面几个点:
- 实现节点高度逐渐变化的动画
- 节点高度不固定时,需要动态获取节点高度
一、实现节点高度逐渐变化的动画
在 Vue 中,我们可以使用 Vue 的内置
<transition>组件来实现展开和收缩的动画效果。监听<transition>组件的生命周期,获取节点高度
<template>
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave">
<slot></slot>
</transition>
</template>
<script>
import { addClass, removeClass } from './index.helper'
export default {
name: 'TransitionCollapse',
methods: {
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) {
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) {
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
}
}
}
</script>
<style lang="less" scoped>
.collapse-transition {
transition: 0.3s height ease-in-out, 0.3s padding-top ease-in-out, 0.3s padding-bottom ease-in-out
}
</style>
const trim = function(string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
}
export function hasClass(el, cls) {
if (!el || !cls) return false
if (cls.includes(' ')) throw new Error('className should not contain space.')
if (el.classList) {
return el.classList.contains(cls)
} else {
return (' ' + el.className + ' ').includes(' ' + cls + ' ')
}
}
export function addClass(el, cls) {
if (!el) return
let curClass = el.className
const classes = (cls || '').split(' ')
for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]
if (!clsName) continue
if (el.classList) {
el.classList.add(clsName)
} else if (!hasClass(el, clsName)) {
curClass += ' ' + clsName
}
}
if (!el.classList) {
el.setAttribute('class', curClass)
}
}
export function removeClass(el, cls) {
if (!el || !cls) return
const classes = cls.split(' ')
let curClass = ' ' + el.className + ' '
for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]
if (!clsName) continue
if (el.classList) {
el.classList.remove(clsName)
} else if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ')
}
}
if (!el.classList) {
el.setAttribute('class', trim(curClass))
}
}
使用
<template>
<CollapseTransition>
<div v-if="collapsed">
<div v-for="item in 10">折叠内容{{item}}</div>
</div>
</CollapseTransition>
<button @tap="handleClick">{{collapsed ? '折叠' : '展开'}}</button>
</template>
<script>
import CollapseTransition form '../../components/CollapseTransition.vue'
export default {
components: {
CollapseTransition
},
data: {
collapsed: false
},
methods: {
handleClick() {
this.collapsed = !this.collapsed
}
}
}
</script>