问题难点
任意高度的内容面板展示/隐藏过程中如何添加过渡动画效果?
大家好,我是webber老丁。相信很多朋友在vue开发一些折叠功能(如菜单、侧边栏抽屉等)时候,为了美观会加上一些过渡效果,比如宽度、高度的过渡等。
以高度为例,一般情况下,如果我们提前知道内容的固定高H,那么可以设置高度从0到H来控制显示隐藏,如下:
js
<template>
<button @click="isShow = !isShow"></button>
... 假设固高度 300px
<div class="content" :style="{ height: isShow ? '300px' : 0 }">
...
</div>
</tempalte>
<script>
...
const isShow = ref(false);
</script>
<style>
.content{
transition: height 0.3s;
}
</style>
但是,很多情况下,内容可能是动态生成的,导致高度未知,以上方案就会失效,即使设置 height 为auto也没效果。
解决思路
这种情况下就需要借助<Transtion>
组件里的钩子周期函数来实现,并封装成组件复用。
1、首先先包装内置的 Transition
组件:
html
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@leave="onLeave"
:css="false">
<slot></slot>
</Transition>
css设置false原因:
(摘自vue官网) 在使用仅由 JavaScript 执行的动画时,最好是添加一个
:css="false"
prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。
2、在 beforeEnter 周期函数里初始化样式。注意有些内容有设置padding
,为了完全隐藏,还需要记录paddingTop
和paddingBottom
js
const originStyle = ref(null);
// 设置元素的 "enter-from" 状态
function onBeforeEnter(el) {
//记录元素原来的属性
if (!originStyle.value) {
const computedStyle = getComputedStyle(el)
originStyle.value = {
paddingTop: computedStyle.paddingTop,
paddingBottom: computedStyle.paddingBottom,
overflow: computedStyle.overflow
}
}
el.style.transition = "height 0.3s, padding 0.3s";
el.style.overflow = 'hidden';
el.style.height = '0';
el.style.paddingTop = '0'
el.style.paddingBottom = '0'
}
3、onEnter周期函数里通过scrollHeight
获取到内容高度
js
function onEnter(el, done) {
// 使用 RAF 保证动画帧同步
requestAnimationFrame(() => {
el.style.height = `${el.scrollHeight}px`;
el.style.paddingTop = originStyle.value.paddingTop;
el.style.paddingBottom = originStyle.value.paddingBottom;
// 监听过渡结束事件
el.addEventListener('transitionend', done, { once: true })
})
}
4、onLeave周期函数 height、padding重置为0
js
function onLeave(el, done) {
// 添加延迟保证过渡生效
requestAnimationFrame(() => {
el.style.height = '0';
el.style.paddingTop = '0';
el.style.paddingBottom = '0';
el.addEventListener('transitionend', done, { once: true })
})
}
测试调用
封装完成后,就可以在任意地方使用,如下:
js
<template>
<button @click="isShow = !isShow"></button>
...
<MyTransition>
<div v-show="isShow" class="content"> //未知高度
...
</div>
</MyTransition>
</tempalte>
<script>
import MyTransition from './MyTransition';
const isShow = ref(false);
</script>
至此,我们就解决了上述问题,完美实现任意内容的显示/隐藏过渡效果。
感谢阅读。