|
@@ -1,286 +1,292 @@
|
|
|
<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
|
|
|
+ duration: {
|
|
|
+ type: Number,
|
|
|
+ default: 300
|
|
|
+ },
|
|
|
+ styles: {
|
|
|
+ type: Object,
|
|
|
+ default () {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ customClass: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ onceRender: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
},
|
|
|
- // 初始化动画条件
|
|
|
- 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
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isShow: false,
|
|
|
+ transform: '',
|
|
|
+ opacity: 0,
|
|
|
+ animationData: {},
|
|
|
+ durationTime: 300,
|
|
|
+ config: {}
|
|
|
}
|
|
|
- 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])
|
|
|
+ watch: {
|
|
|
+ show: {
|
|
|
+ handler(newVal) {
|
|
|
+ if (newVal) {
|
|
|
+ this.open()
|
|
|
+ } else {
|
|
|
+ // 避免上来就执行 close,导致动画错乱
|
|
|
+ if (this.isShow) {
|
|
|
+ this.close()
|
|
|
+ }
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- console.error(`方法 ${i} 不存在`)
|
|
|
- }
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
}
|
|
|
- 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
|
|
|
+ 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
|
|
|
}
|
|
|
- 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: ''
|
|
|
+ created() {
|
|
|
+ // 动画默认配置
|
|
|
+ this.config = {
|
|
|
+ duration: this.duration,
|
|
|
+ timingFunction: 'ease',
|
|
|
+ transformOrigin: '50% 50%',
|
|
|
+ delay: 0
|
|
|
}
|
|
|
- let buildStyle = (type, mode) => {
|
|
|
- if (mode === 'fade') {
|
|
|
- styles.opacity = this.animationType(type)[mode]
|
|
|
- } else {
|
|
|
- styles.transform += this.animationType(type)[mode] + ' '
|
|
|
+ this.durationTime = this.duration
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ /**
|
|
|
+ * ref 触发 初始化动画
|
|
|
+ */
|
|
|
+ init(obj = {}) {
|
|
|
+ if (obj.duration) {
|
|
|
+ this.durationTime = obj.duration
|
|
|
}
|
|
|
- }
|
|
|
- if (typeof this.modeClass === 'string') {
|
|
|
- buildStyle(type, this.modeClass)
|
|
|
- } else {
|
|
|
- this.modeClass.forEach(mode => {
|
|
|
- buildStyle(type, mode)
|
|
|
+ this.animation = createAnimation(Object.assign(this.config, obj), this)
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 点击组件触发回调
|
|
|
+ */
|
|
|
+ onClick() {
|
|
|
+ this.$emit('click', {
|
|
|
+ detail: this.isShow
|
|
|
})
|
|
|
- }
|
|
|
- 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
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 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)
|
|
|
}
|
|
|
- if (mode === 'slide-right') {
|
|
|
- aniNum = type ? '100%' : '0'
|
|
|
+ })
|
|
|
+ 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
|
|
|
+
|
|
|
+ // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
|
|
|
+ this.timer = setTimeout(() => {
|
|
|
+ this.animation = createAnimation(this.config, this)
|
|
|
+ this.tranfromInit(false).step()
|
|
|
+ this.animation.run(() => {
|
|
|
+ // #ifdef APP-NVUE
|
|
|
+ this.transform = this.styleInit(false).transform || ''
|
|
|
+ this.opacity = this.styleInit(false).opacity || 1
|
|
|
+ // #endif
|
|
|
+ // #ifndef APP-NVUE
|
|
|
+ this.transform = ''
|
|
|
+ this.opacity = this.styleInit(false).opacity || 1
|
|
|
+ // #endif
|
|
|
+ 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 (mode === 'slide-bottom') {
|
|
|
- aniNum = type ? '100%' : '0'
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)
|
|
|
+ })
|
|
|
}
|
|
|
- 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 ? 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})`
|
|
|
- }
|
|
|
- },
|
|
|
- // 内置动画类型与实际动画对应字典
|
|
|
- animationMode() {
|
|
|
- return {
|
|
|
- fade: 'opacity',
|
|
|
- 'slide-top': 'translateY',
|
|
|
- 'slide-right': 'translateX',
|
|
|
- 'slide-bottom': 'translateY',
|
|
|
- 'slide-left': 'translateX',
|
|
|
- 'zoom-in': 'scale',
|
|
|
- 'zoom-out': 'scale'
|
|
|
+ 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()
|
|
|
}
|
|
|
- },
|
|
|
- // 驼峰转中横线
|
|
|
- toLine(name) {
|
|
|
- return name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
</script>
|
|
|
|
|
|
<style></style>
|