<template> 
 | 
  <!-- #ifndef APP-NVUE --> 
 | 
  <view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> 
 | 
  <!-- #endif --> 
 | 
  <!-- #ifdef APP-NVUE --> 
 | 
  <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> 
 | 
  <!-- #endif --> 
 | 
</template> 
 | 
  
 | 
<script> 
 | 
import { createAnimation } from './createAnimation' 
 | 
  
 | 
/** 
 | 
 * Transition 过渡动画 
 | 
 * @description 简单过渡动画组件 
 | 
 * @tutorial https://ext.dcloud.net.cn/plugin?id=985 
 | 
 * @property {Boolean} show = [false|true] 控制组件显示或隐藏 
 | 
 * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 
 | 
 *  @value fade 渐隐渐出过渡 
 | 
 *  @value slide-top 由上至下过渡 
 | 
 *  @value slide-right 由右至左过渡 
 | 
 *  @value slide-bottom 由下至上过渡 
 | 
 *  @value slide-left 由左至右过渡 
 | 
 *  @value zoom-in 由小到大过渡 
 | 
 *  @value zoom-out 由大到小过渡 
 | 
 * @property {Number} duration 过渡动画持续时间 
 | 
 * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` 
 | 
 */ 
 | 
export default { 
 | 
    name: 'uniTransition', 
 | 
    emits:['click','change'], 
 | 
    props: { 
 | 
        show: { 
 | 
            type: Boolean, 
 | 
            default: false 
 | 
        }, 
 | 
        modeClass: { 
 | 
            type: [Array, String], 
 | 
            default() { 
 | 
                return 'fade' 
 | 
            } 
 | 
        }, 
 | 
        duration: { 
 | 
            type: Number, 
 | 
            default: 300 
 | 
        }, 
 | 
        styles: { 
 | 
            type: Object, 
 | 
            default() { 
 | 
                return {} 
 | 
            } 
 | 
        }, 
 | 
        customClass:{ 
 | 
            type: String, 
 | 
            default: '' 
 | 
        }, 
 | 
        onceRender:{ 
 | 
            type:Boolean, 
 | 
            default:false 
 | 
        }, 
 | 
    }, 
 | 
    data() { 
 | 
        return { 
 | 
            isShow: false, 
 | 
            transform: '', 
 | 
            opacity: 1, 
 | 
            animationData: {}, 
 | 
            durationTime: 300, 
 | 
            config: {} 
 | 
        } 
 | 
    }, 
 | 
    watch: { 
 | 
        show: { 
 | 
            handler(newVal) { 
 | 
                if (newVal) { 
 | 
                    this.open() 
 | 
                } else { 
 | 
                    // 避免上来就执行 close,导致动画错乱 
 | 
                    if (this.isShow) { 
 | 
                        this.close() 
 | 
                    } 
 | 
                } 
 | 
            }, 
 | 
            immediate: true 
 | 
        } 
 | 
    }, 
 | 
    computed: { 
 | 
        // 生成样式数据 
 | 
        stylesObject() { 
 | 
            let styles = { 
 | 
                ...this.styles, 
 | 
                'transition-duration': this.duration / 1000 + 's' 
 | 
            } 
 | 
            let transform = '' 
 | 
            for (let i in styles) { 
 | 
                let line = this.toLine(i) 
 | 
                transform += line + ':' + styles[i] + ';' 
 | 
            } 
 | 
            return transform 
 | 
        }, 
 | 
        // 初始化动画条件 
 | 
        transformStyles() { 
 | 
            return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject 
 | 
        } 
 | 
    }, 
 | 
    created() { 
 | 
        // 动画默认配置 
 | 
        this.config = { 
 | 
            duration: this.duration, 
 | 
            timingFunction: 'ease', 
 | 
            transformOrigin: '50% 50%', 
 | 
            delay: 0 
 | 
        } 
 | 
        this.durationTime = this.duration 
 | 
    }, 
 | 
    methods: { 
 | 
        /** 
 | 
         *  ref 触发 初始化动画 
 | 
         */ 
 | 
        init(obj = {}) { 
 | 
            if (obj.duration) { 
 | 
                this.durationTime = obj.duration 
 | 
            } 
 | 
            this.animation = createAnimation(Object.assign(this.config, obj),this) 
 | 
        }, 
 | 
        /** 
 | 
         * 点击组件触发回调 
 | 
         */ 
 | 
        onClick() { 
 | 
            this.$emit('click', { 
 | 
                detail: this.isShow 
 | 
            }) 
 | 
        }, 
 | 
        /** 
 | 
         * ref 触发 动画分组 
 | 
         * @param {Object} obj 
 | 
         */ 
 | 
        step(obj, config = {}) { 
 | 
            if (!this.animation) return 
 | 
            for (let i in obj) { 
 | 
                try { 
 | 
                    if(typeof obj[i] === 'object'){ 
 | 
                        this.animation[i](...obj[i]) 
 | 
                    }else{ 
 | 
                        this.animation[i](obj[i]) 
 | 
                    } 
 | 
                } catch (e) { 
 | 
                    console.error(`方法 ${i} 不存在`) 
 | 
                } 
 | 
            } 
 | 
            this.animation.step(config) 
 | 
            return this 
 | 
        }, 
 | 
        /** 
 | 
         *  ref 触发 执行动画 
 | 
         */ 
 | 
        run(fn) { 
 | 
            if (!this.animation) return 
 | 
            this.animation.run(fn) 
 | 
        }, 
 | 
        // 开始过度动画 
 | 
        open() { 
 | 
            clearTimeout(this.timer) 
 | 
            this.transform = '' 
 | 
            this.isShow = true 
 | 
            let { opacity, transform } = this.styleInit(false) 
 | 
            if (typeof opacity !== 'undefined') { 
 | 
                this.opacity = opacity 
 | 
            } 
 | 
            this.transform = transform 
 | 
            // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常 
 | 
            this.$nextTick(() => { 
 | 
                // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器 
 | 
                this.timer = setTimeout(() => { 
 | 
                    this.animation = createAnimation(this.config, this) 
 | 
                    this.tranfromInit(false).step() 
 | 
                    this.animation.run() 
 | 
                    this.$emit('change', { 
 | 
                        detail: this.isShow 
 | 
                    }) 
 | 
                }, 20) 
 | 
            }) 
 | 
        }, 
 | 
        // 关闭过度动画 
 | 
        close(type) { 
 | 
            if (!this.animation) return 
 | 
            this.tranfromInit(true) 
 | 
                .step() 
 | 
                .run(() => { 
 | 
                    this.isShow = false 
 | 
                    this.animationData = null 
 | 
                    this.animation = null 
 | 
                    let { opacity, transform } = this.styleInit(false) 
 | 
                    this.opacity = opacity || 1 
 | 
                    this.transform = transform 
 | 
                    this.$emit('change', { 
 | 
                        detail: this.isShow 
 | 
                    }) 
 | 
                }) 
 | 
        }, 
 | 
        // 处理动画开始前的默认样式 
 | 
        styleInit(type) { 
 | 
            let styles = { 
 | 
                transform: '' 
 | 
            } 
 | 
            let buildStyle = (type, mode) => { 
 | 
                if (mode === 'fade') { 
 | 
                    styles.opacity = this.animationType(type)[mode] 
 | 
                } else { 
 | 
                    styles.transform += this.animationType(type)[mode] + ' ' 
 | 
                } 
 | 
            } 
 | 
            if (typeof this.modeClass === 'string') { 
 | 
                buildStyle(type, this.modeClass) 
 | 
            } else { 
 | 
                this.modeClass.forEach(mode => { 
 | 
                    buildStyle(type, mode) 
 | 
                }) 
 | 
            } 
 | 
            return styles 
 | 
        }, 
 | 
        // 处理内置组合动画 
 | 
        tranfromInit(type) { 
 | 
            let buildTranfrom = (type, mode) => { 
 | 
                let aniNum = null 
 | 
                if (mode === 'fade') { 
 | 
                    aniNum = type ? 0 : 1 
 | 
                } else { 
 | 
                    aniNum = type ? '-100%' : '0' 
 | 
                    if (mode === 'zoom-in') { 
 | 
                        aniNum = type ? 0.8 : 1 
 | 
                    } 
 | 
                    if (mode === 'zoom-out') { 
 | 
                        aniNum = type ? 1.2 : 1 
 | 
                    } 
 | 
                    if (mode === 'slide-right') { 
 | 
                        aniNum = type ? '100%' : '0' 
 | 
                    } 
 | 
                    if (mode === 'slide-bottom') { 
 | 
                        aniNum = type ? '100%' : '0' 
 | 
                    } 
 | 
                } 
 | 
                this.animation[this.animationMode()[mode]](aniNum) 
 | 
            } 
 | 
            if (typeof this.modeClass === 'string') { 
 | 
                buildTranfrom(type, this.modeClass) 
 | 
            } else { 
 | 
                this.modeClass.forEach(mode => { 
 | 
                    buildTranfrom(type, mode) 
 | 
                }) 
 | 
            } 
 | 
  
 | 
            return this.animation 
 | 
        }, 
 | 
        animationType(type) { 
 | 
            return { 
 | 
                fade: type ? 1 : 0, 
 | 
                'slide-top': `translateY(${type ? '0' : '-100%'})`, 
 | 
                'slide-right': `translateX(${type ? '0' : '100%'})`, 
 | 
                'slide-bottom': `translateY(${type ? '0' : '100%'})`, 
 | 
                'slide-left': `translateX(${type ? '0' : '-100%'})`, 
 | 
                'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, 
 | 
                'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` 
 | 
            } 
 | 
        }, 
 | 
        // 内置动画类型与实际动画对应字典 
 | 
        animationMode() { 
 | 
            return { 
 | 
                fade: 'opacity', 
 | 
                'slide-top': 'translateY', 
 | 
                'slide-right': 'translateX', 
 | 
                'slide-bottom': 'translateY', 
 | 
                'slide-left': 'translateX', 
 | 
                'zoom-in': 'scale', 
 | 
                'zoom-out': 'scale' 
 | 
            } 
 | 
        }, 
 | 
        // 驼峰转中横线 
 | 
        toLine(name) { 
 | 
            return name.replace(/([A-Z])/g, '-$1').toLowerCase() 
 | 
        } 
 | 
    } 
 | 
} 
 | 
</script> 
 | 
  
 | 
<style></style> 
 |