vue创建可复用的slideUp和slideDown动画组件

template结构

<template>
    <transition
        name="laoq-transition-collapse"
        mode="out-in"
        @before-enter="beforeEnter"
        @enter="enter"
        @after-enter="afterEnter"
        @before-leave="beforeLeave"
        @leave="leave"
        @after-leave="afterLeave"
    >
        <slot></slot>
    </transition>
</template>

script代码

<script>
import LaoqUtil from '../../../utils/util';
import './collapse.less';

export default {
    name: 'laoq-transition-collapse',
    data() {
        return {
            animation_ing: false    //记录当前是否处于动画中,如果是则切换时跳过动画,避免频繁切换时paddingTop和paddingBottom来不及还原导致显示失真
        };
    },
    methods: {
        beforeEnter(el) {
            //显示动画开始的状态
            if (this.animation_ing) return;

            this.animation_ing = true;
            LaoqUtil.addClass(el, 'laoq-transition-collapse');

            !el.dataset && (el.dataset = {});
            el.dataset.paddingTop = el.style.paddingTop;
            el.dataset.paddingBottom = el.style.paddingBottom;
            el.dataset.overflow = el.style.overflow;
            el.dataset.paddingTopComputed = LaoqUtil.getCSS(el, 'padding-top');
            el.dataset.paddingBottomComputed = LaoqUtil.getCSS(el, 'padding-bottom');

            el.style.height = 0;
            el.style.paddingTop = 0;
            el.style.paddingBottom = 0;
            el.style.overflow = 'hidden';
        },
        enter(el) {
            //显示动画结束的状态
            let scroll_height = el.scrollHeight,
                scroll_height_computed = el.scrollHeight + parseFloat(el.dataset.paddingTopComputed) + parseFloat(el.dataset.paddingBottomComputed);

            /*
             * 元素由隐藏到显示的过程中,设置的paddingTop和paddingBottom会有延迟
             * 此时取到的scrollHeight可能只是元素内容的高度并不包含paddingTop和paddingBottom
            */
            el.style.height = Math.max(scroll_height, scroll_height_computed) + 'px';
            el.style.paddingTop = el.dataset.paddingTop;
            el.style.paddingBottom = el.dataset.paddingBottom;
        },
        afterEnter(el) {
            //显示动画结束后的收尾工作
            LaoqUtil.removeClass(el, 'laoq-transition-collapse');

            el.style.height = '';
            el.style.overflow = el.dataset.overflow;

            delete el.dataset.paddingTop;
            delete el.dataset.paddingBottom;
            delete el.dataset.overflow;
            delete el.dataset.paddingTopComputed;
            delete el.dataset.paddingBottomComputed;

            this.animation_ing = false;
        },

        beforeLeave(el) {
            //隐藏动画开始的状态
            if (this.animation_ing) return;

            this.animation_ing = true;
            LaoqUtil.addClass(el, 'laoq-transition-collapse');

            !el.dataset && (el.dataset = {});
            el.dataset.paddingTop = el.style.paddingTop;
            el.dataset.paddingBottom = el.style.paddingBottom;
            el.dataset.overflow = el.style.overflow;

            el.style.height = el.scrollHeight > 0 ? el.scrollHeight + 'px' : '';
            el.style.overflow = 'hidden';
        },
        leave(el) {
            //隐藏动画结束的状态
            el.style.height = el.scrollHeight > 0 ? 0 : '';
            el.style.paddingTop = 0;
            el.style.paddingBottom = 0;
        },
        afterLeave(el) {
            //隐藏动画结束后的收尾工作
            LaoqUtil.removeClass(el, 'laoq-transition-collapse');

            el.style.height = '';
            el.style.paddingTop = el.dataset.paddingTop;
            el.style.paddingBottom = el.dataset.paddingBottom;
            el.style.overflow = el.dataset.overflow;

            delete el.dataset.paddingTop;
            delete el.dataset.paddingBottom;
            delete el.dataset.overflow;

            this.animation_ing = false;
        }
    }
}
</script>

util.js

'use strict';

const LaoqUtil = {
    addClass(ele, cls) {
        if (!ele || !cls) return;

        cls = cls.trim();
        let cur_class = ele.className;

        cls.split(' ').forEach(item => {
            if (!item) return true;

            if (ele.classList) {
                ele.classList.add(item);
            } else {
                cur_class += ' ' + item;
            }
        });

        !ele.classList && (ele.className = cur_class);
    },

    removeClass(ele, cls) {
        if (!ele || !cls) return;

        cls = cls.trim();
        let cur_class = ' ' + ele.className + ' ';
        cls.split(' ').forEach(item => {
            if (!item) return true;

            if (ele.classList) {
                ele.classList.remove(item);
            } else {
                cur_class = cur_class.replace(' ' + item + ' ', '');
            }
        });

        !ele.classList && (ele.className = cur_class.trim());
    },

    getCSS(ele, prop) {
        return window.getComputedStyle(ele, null).getPropertyValue(prop) || '';
    }
};

export default LaoqUtil;

collapse.less

.laoq-transition-collapse {
    -webkit-transition: height 0.4s ease-in-out, padding-top 0.4s ease-in-out,
        padding-bottom 0.4s ease-in-out;
    -moz-transition: height 0.4s ease-in-out, padding-top 0.4s ease-in-out,
        padding-bottom 0.4s ease-in-out;
    transition: height 0.4s ease-in-out, padding-top 0.4s ease-in-out,
        padding-bottom 0.4s ease-in-out;
}

测试

<template>
    <div>
        <div style="margin:20px 0;">
            <laoq-button @click="click">slide</laoq-button>
        </div>

        <laoq-transition-collapse>
            <div class="main" v-show="show">
                我是DIV<br/>我是DIV<br/>我是DIV<br/>
            </div>
        </laoq-transition-collapse>
    </div>
</template>

<script>
import LaoqTransitionCollapse from '../components/transition/collapse/collapse.vue';

export default {
    components:{
        LaoqTransitionCollapse
    },
    data() {
        return {
            show: true
        };
    },
    methods: {
        click() {
            this.show = !this.show;
        }
    }
}
</script>

<style lang="less" scoped>
.main {
    width: 200px;
    padding:50px;
    text-align: center;
    color: #fff;
    background: #17a2b8;
}
</style>

效果

01-03 20:49