| <template> | 
|     <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> | 
| </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: '' | 
|         } | 
|     }, | 
|     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> |