3 changed files with 337 additions and 294 deletions
@ -1,286 +1,292 @@ |
|||||
<template> |
<template> |
||||
<!-- #ifndef APP-NVUE --> |
<!-- #ifndef APP-NVUE --> |
||||
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> |
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"> |
||||
<!-- #endif --> |
<slot></slot> |
||||
<!-- #ifdef APP-NVUE --> |
</view> |
||||
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view> |
<!-- #endif --> |
||||
<!-- #endif --> |
<!-- #ifdef APP-NVUE --> |
||||
|
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
|
<!-- #endif --> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
import { createAnimation } from './createAnimation' |
import { createAnimation } from './createAnimation' |
||||
|
|
||||
/** |
/** |
||||
* Transition 过渡动画 |
* Transition 过渡动画 |
||||
* @description 简单过渡动画组件 |
* @description 简单过渡动画组件 |
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
* @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
||||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
* @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
||||
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
||||
* @value fade 渐隐渐出过渡 |
* @value fade 渐隐渐出过渡 |
||||
* @value slide-top 由上至下过渡 |
* @value slide-top 由上至下过渡 |
||||
* @value slide-right 由右至左过渡 |
* @value slide-right 由右至左过渡 |
||||
* @value slide-bottom 由下至上过渡 |
* @value slide-bottom 由下至上过渡 |
||||
* @value slide-left 由左至右过渡 |
* @value slide-left 由左至右过渡 |
||||
* @value zoom-in 由小到大过渡 |
* @value zoom-in 由小到大过渡 |
||||
* @value zoom-out 由大到小过渡 |
* @value zoom-out 由大到小过渡 |
||||
* @property {Number} duration 过渡动画持续时间 |
* @property {Number} duration 过渡动画持续时间 |
||||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
||||
*/ |
*/ |
||||
export default { |
export default { |
||||
name: 'uniTransition', |
name: 'uniTransition', |
||||
emits:['click','change'], |
emits: ['click', 'change'], |
||||
props: { |
props: { |
||||
show: { |
show: { |
||||
type: Boolean, |
type: Boolean, |
||||
default: false |
default: false |
||||
}, |
}, |
||||
modeClass: { |
modeClass: { |
||||
type: [Array, String], |
type: [Array, String], |
||||
default() { |
default () { |
||||
return 'fade' |
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 |
duration: { |
||||
} |
type: Number, |
||||
}, |
default: 300 |
||||
computed: { |
}, |
||||
// 生成样式数据 |
styles: { |
||||
stylesObject() { |
type: Object, |
||||
let styles = { |
default () { |
||||
...this.styles, |
return {} |
||||
'transition-duration': this.duration / 1000 + 's' |
} |
||||
} |
}, |
||||
let transform = '' |
customClass: { |
||||
for (let i in styles) { |
type: String, |
||||
let line = this.toLine(i) |
default: '' |
||||
transform += line + ':' + styles[i] + ';' |
}, |
||||
} |
onceRender: { |
||||
return transform |
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
}, |
}, |
||||
// 初始化动画条件 |
data() { |
||||
transformStyles() { |
return { |
||||
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject |
isShow: false, |
||||
} |
transform: '', |
||||
}, |
opacity: 0, |
||||
created() { |
animationData: {}, |
||||
// 动画默认配置 |
durationTime: 300, |
||||
this.config = { |
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) |
|
||||
}, |
}, |
||||
/** |
watch: { |
||||
* 点击组件触发回调 |
show: { |
||||
*/ |
handler(newVal) { |
||||
onClick() { |
if (newVal) { |
||||
this.$emit('click', { |
this.open() |
||||
detail: this.isShow |
} else { |
||||
}) |
// 避免上来就执行 close,导致动画错乱 |
||||
}, |
if (this.isShow) { |
||||
/** |
this.close() |
||||
* 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} 不存在`) |
immediate: true |
||||
} |
|
||||
} |
} |
||||
this.animation.step(config) |
|
||||
return this |
|
||||
}, |
}, |
||||
/** |
computed: { |
||||
* ref 触发 执行动画 |
// 生成样式数据 |
||||
*/ |
stylesObject() { |
||||
run(fn) { |
let styles = { |
||||
if (!this.animation) return |
...this.styles, |
||||
this.animation.run(fn) |
'transition-duration': this.duration / 1000 + 's' |
||||
}, |
} |
||||
// 开始过度动画 |
let transform = '' |
||||
open() { |
for (let i in styles) { |
||||
clearTimeout(this.timer) |
let line = this.toLine(i) |
||||
this.transform = '' |
transform += line + ':' + styles[i] + ';' |
||||
this.isShow = true |
} |
||||
let { opacity, transform } = this.styleInit(false) |
return transform |
||||
if (typeof opacity !== 'undefined') { |
}, |
||||
this.opacity = opacity |
// 初始化动画条件 |
||||
|
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) |
|
||||
}) |
|
||||
}, |
}, |
||||
// 关闭过度动画 |
created() { |
||||
close(type) { |
// 动画默认配置 |
||||
if (!this.animation) return |
this.config = { |
||||
this.tranfromInit(true) |
duration: this.duration, |
||||
.step() |
timingFunction: 'ease', |
||||
.run(() => { |
transformOrigin: '50% 50%', |
||||
this.isShow = false |
delay: 0 |
||||
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) => { |
this.durationTime = this.duration |
||||
if (mode === 'fade') { |
}, |
||||
styles.opacity = this.animationType(type)[mode] |
methods: { |
||||
} else { |
/** |
||||
styles.transform += this.animationType(type)[mode] + ' ' |
* ref 触发 初始化动画 |
||||
|
*/ |
||||
|
init(obj = {}) { |
||||
|
if (obj.duration) { |
||||
|
this.durationTime = obj.duration |
||||
} |
} |
||||
} |
this.animation = createAnimation(Object.assign(this.config, obj), this) |
||||
if (typeof this.modeClass === 'string') { |
}, |
||||
buildStyle(type, this.modeClass) |
/** |
||||
} else { |
* 点击组件触发回调 |
||||
this.modeClass.forEach(mode => { |
*/ |
||||
buildStyle(type, mode) |
onClick() { |
||||
|
this.$emit('click', { |
||||
|
detail: this.isShow |
||||
}) |
}) |
||||
} |
}, |
||||
return styles |
/** |
||||
}, |
* ref 触发 动画分组 |
||||
// 处理内置组合动画 |
* @param {Object} obj |
||||
tranfromInit(type) { |
*/ |
||||
let buildTranfrom = (type, mode) => { |
step(obj, config = {}) { |
||||
let aniNum = null |
if (!this.animation) return this |
||||
if (mode === 'fade') { |
Object.keys(obj).forEach(key => { |
||||
aniNum = type ? 0 : 1 |
const value = obj[key] |
||||
} else { |
if (typeof this.animation[key] === 'function') { |
||||
aniNum = type ? '-100%' : '0' |
Array.isArray(value) ? |
||||
if (mode === 'zoom-in') { |
this.animation[key](...value) : |
||||
aniNum = type ? 0.8 : 1 |
this.animation[key](value) |
||||
} |
|
||||
if (mode === 'zoom-out') { |
|
||||
aniNum = type ? 1.2 : 1 |
|
||||
} |
} |
||||
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 |
return this.animation |
||||
}, |
}, |
||||
animationType(type) { |
animationType(type) { |
||||
return { |
return { |
||||
fade: type ? 1 : 0, |
fade: type ? 1 : 0, |
||||
'slide-top': `translateY(${type ? '0' : '-100%'})`, |
'slide-top': `translateY(${type ? '0' : '-100%'})`, |
||||
'slide-right': `translateX(${type ? '0' : '100%'})`, |
'slide-right': `translateX(${type ? '0' : '100%'})`, |
||||
'slide-bottom': `translateY(${type ? '0' : '100%'})`, |
'slide-bottom': `translateY(${type ? '0' : '100%'})`, |
||||
'slide-left': `translateX(${type ? '0' : '-100%'})`, |
'slide-left': `translateX(${type ? '0' : '-100%'})`, |
||||
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, |
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`, |
||||
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` |
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})` |
||||
} |
} |
||||
}, |
}, |
||||
// 内置动画类型与实际动画对应字典 |
// 内置动画类型与实际动画对应字典 |
||||
animationMode() { |
animationMode() { |
||||
return { |
return { |
||||
fade: 'opacity', |
fade: 'opacity', |
||||
'slide-top': 'translateY', |
'slide-top': 'translateY', |
||||
'slide-right': 'translateX', |
'slide-right': 'translateX', |
||||
'slide-bottom': 'translateY', |
'slide-bottom': 'translateY', |
||||
'slide-left': 'translateX', |
'slide-left': 'translateX', |
||||
'zoom-in': 'scale', |
'zoom-in': 'scale', |
||||
'zoom-out': '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> |
</script> |
||||
|
|
||||
<style></style> |
<style></style> |
||||
|
|||||
Loading…
Reference in new issue