vr-shopxo-uniapp/pages/plugins/live/pull/components/transition/transition.vue

396 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<!-- #ifndef APP-NVUE -->
<view v-if="isShow" ref="ani" class="page-width-max" :animation="animationData" :class="propCustomClass" :style="transformStyles" @click="onClick">
<slot></slot>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view v-if="isShow" ref="ani" class="page-width-max" :animation="animationData" :class="propCustomClass" :style="transformStyles" @click="onClick">
<slot></slot>
</view>
<!-- #endif -->
</template>
<script>
import { createAnimation } from './createAnimation';
/**
* transition 动画组件
* @description
* @tutorial
* @property {Boolean} show 控制组件显示或关闭 (默认 false
* @property {Array | String} mode 内置过渡动画类型 (默认 'fade'
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value slide-right 由右至左过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {String | Number} duration 动画的执行时间单位ms (默认 300
* @property {Object} customStyle 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
* @property {String} timingFunction 使用的动画过渡函数 (默认 'ease-out'
* @property {String} customClass 自定义类名
* @event {Function} click 点击组件触发
* @event {Function} change 过渡动画结束时触发
* @example
*/
export default {
name: 'u-transition',
emits: ['click', 'change'],
props: {
// 是否展示组件
propShow: {
type: Boolean,
default: false,
},
// 使用的动画模式
propMode: {
type: [Array, String, null],
default() {
return 'fade';
},
},
// 动画的执行时间单位ms
propDuration: {
type: [String, Number],
default: 300,
},
// 使用的动画过渡函数
propTimingFunction: {
type: String,
default: 'ease-out',
},
propCustomStyle: {
type: Object,
default() {
return {};
},
},
propCustomClass: {
type: String,
default: '',
},
// nvue模式下 是否直接显示在uv-list等cell下面使用就需要设置
propCellChild: {
type: Boolean,
default: false,
},
},
data() {
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {},
};
},
watch: {
propShow: {
handler(newVal) {
if (newVal) {
this.open();
} else {
// 避免上来就执行 close,导致动画错乱
if (this.isShow) {
this.close();
}
}
},
immediate: true,
},
},
computed: {
// 初始化动画条件
transformStyles() {
const style = {
transform: this.transform,
opacity: this.opacity,
...this.addStyle(this.propCustomStyle),
'transition-duration': `${this.propDuration / 1000}s`,
};
return this.addStyle(style, 'string');
},
},
created() {
// 动画默认配置
this.config = {
duration: this.propDuration,
timingFunction: this.propTimingFunction,
transformOrigin: '50% 50%',
delay: 0,
};
this.durationTime = this.propDuration;
},
methods: {
/**
* @description 样式转换
* 对象转字符串,或者字符串转对象
* @param {object | string} customStyle 需要转换的目标
* @param {String} target 转换的目的object-转为对象string-转为字符串
* @returns {object|string}
*/
addStyle (customStyle, target = 'object') {
// 字符串转字符串,对象转对象情形,直接返回
if (this.empty(customStyle) || typeof (customStyle) === 'object' && target === 'object' || target === 'string' &&
typeof (customStyle) === 'string') {
return customStyle
}
// 字符串转对象
if (target === 'object') {
// 去除字符串样式中的两端空格(中间的空格不能去掉比如padding: 20px 0如果去掉了就错了),空格是无用的
customStyle = this.trim(customStyle)
// 根据";"将字符串转为数组形式
const styleArray = customStyle.split(';')
const style = {}
// 历遍数组,拼接成对象
for (let i = 0; i < styleArray.length; i++) {
// 'font-size:20px;color:red;',如此最后字符串有";"的话会导致styleArray最后一个元素为空字符串这里需要过滤
if (styleArray[i]) {
const item = styleArray[i].split(':')
style[this.trim(item[0])] = this.trim(item[1])
}
}
return style
}
// 这里为对象转字符串形式
let string = ''
for (const i in customStyle) {
// 驼峰转为中划线的形式否则css内联样式无法识别驼峰样式属性名
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
string += `${key}:${customStyle[i]};`
}
// 去除两端空格
return this.trim(string)
},
/**
* 判断是否为空
*/
empty (value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
},
/**
* @description 去除空格
* @param {String} str 需要去除空格的字符串
* @param {String} pos both(左右)|left|right|all 默认both
*/
trim (str, pos = 'both') {
str = String(str)
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, '')
}
if (pos == 'left') {
return str.replace(/^\s*/, '')
}
if (pos == 'right') {
return str.replace(/(\s*$)/g, '')
}
if (pos == 'all') {
return str.replace(/\s+/g, '')
}
return str
},
/**
* 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) {}
}
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();
// #ifdef APP-NVUE
if (this.propCellChild) {
this.opacity = 1;
} else {
this.animation.run();
}
// #endif
// #ifndef APP-NVUE
this.animation.run();
// #endif
// #ifdef VUE3
// #ifdef H5
this.opacity = 1;
// #endif
// #endif
this.$emit('change', {
detail: this.isShow,
});
// #ifdef H5
// #ifdef VUE3
this.transform = '';
// #endif
// #endif
}, 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.propMode === 'string') {
buildStyle(type, this.propMode);
} else {
this.propMode.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.propMode === 'string') {
buildTranfrom(type, this.propMode);
} else {
this.propMode.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>