绿满眶商城微信小程序-uniapp
zhanghua
5 天以前 1113721c0e068c57adbc15149cce15563960a7f2
uni_modules/uni-transition/components/uni-transition/uni-transition.vue
@@ -1,289 +1,286 @@
<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 -->
   <!-- #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'
   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()
               }
   /**
    * 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'
            }
         },
         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} 不存在`)
         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: 0,
            animationData: {},
            durationTime: 300,
            config: {}
         }
         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
      watch: {
         show: {
            handler(newVal) {
               if (newVal) {
                  this.open()
               } else {
                  // 避免上来就执行 close,导致动画错乱
                  if (this.isShow) {
                     this.close()
                  }
               }
            },
            immediate: true
         }
         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.transform = ''
                  this.opacity = opacity || 1
               })
               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] + ' '
      computed: {
         // 生成样式数据
         stylesObject() {
            let styles = {
               ...this.styles,
               'transition-duration': this.duration / 1000 + 's'
            }
         }
         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'
               }
            let transform = ''
            for (let i in styles) {
               let line = this.toLine(i)
               transform += line + ':' + styles[i] + ';'
            }
            this.animation[this.animationMode()[mode]](aniNum)
            return transform
         },
         // 初始化动画条件
         transformStyles() {
            return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
         }
         if (typeof this.modeClass === 'string') {
            buildTranfrom(type, this.modeClass)
         } else {
            this.modeClass.forEach(mode => {
               buildTranfrom(type, mode)
      },
      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 this
            Object.keys(obj).forEach(key => {
               const value = obj[key]
               if (typeof this.animation[key] === 'function') {
                  Array.isArray(value) ?
                     this.animation[key](...value) :
                     this.animation[key](value)
               }
            })
            this.animation.step(config)
            return this
         },
         /**
          *  ref 触发 执行动画
          */
         run(fn) {
            if (!this.animation) return
            this.animation.run(fn)
         },
         // 开始过度动画
         open() {
            clearTimeout(this.timer)
            this.isShow = true
            // 新增初始状态重置逻辑(关键)
            this.transform = this.styleInit(false).transform || ''
            this.opacity = this.styleInit(false).opacity || 0
         return this.animation
      },
      animationType(type) {
         return {
            fade: type ? 0 : 1,
            '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})`
            // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
            this.$nextTick(() => {
               // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
               this.timer = setTimeout(() => {
                  this.animation = createAnimation(this.config, this)
                  this.tranfromInit(false).step()
                  this.animation.run(() => {
                     this.transform = ''
                     this.opacity = this.styleInit(false).opacity || 1
                     this.$emit('change', {
                        detail: this.isShow
                     })
                  })
               }, 80)
            })
         },
         // 关闭过度动画
         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: '', opacity: 1 }
            const buildStyle = (type, mode) => {
               const value = this.animationType(type)[mode] // 直接使用 type 控制状态
               if (mode.startsWith('fade')) {
                  styles.opacity = value
               } else {
                  styles.transform += value + ' '
               }
            }
            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()
         }
      },
      // 内置动画类型与实际动画对应字典
      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>