gaoluyang
3 天以前 92230c9a97dc9ce9df3313d11d26999c04bb6b26
src/uni_modules/uni-transition/components/uni-transition/uni-transition.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,277 @@
<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>