You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

188 lines
5.2 KiB
Vue

<template>
<view v-if="inited" :class="rootClass" :style="style" @transitionend="onTransitionEnd" @click="handleClick">
<slot />
</view>
</template>
<script lang="ts">
export default {
name: 'wd-transition',
options: {
addGlobalClass: true,
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, onBeforeMount, ref, watch } from 'vue'
import { isObj, requestAnimationFrame } from '../common/util'
const getClassNames = (name) => {
if (!name) {
return {
enter: `${props.enterClass} ${props.enterActiveClass}`,
'enter-to': `${props.enterToClass} ${props.enterActiveClass}`,
leave: `${props.leaveClass} ${props.leaveActiveClass}`,
'leave-to': `${props.leaveToClass} ${props.leaveActiveClass}`
}
}
return {
enter: `wd-${name}-enter wd-${name}-enter-active`,
'enter-to': `wd-${name}-enter-to wd-${name}-enter-active`,
leave: `wd-${name}-leave wd-${name}-leave-active`,
'leave-to': `wd-${name}-leave-to wd-${name}-leave-active`
}
}
type TransitionName =
| 'fade'
| 'fade-down'
| 'fade-left'
| 'fade-right'
| 'fade-up'
| 'slide-down'
| 'slide-left'
| 'slide-right'
| 'slide-up'
| 'zoom-in'
| 'zoom-out'
interface Props {
show?: boolean
duration?: Record<string, number> | number | boolean
name?: TransitionName | ''
customStyle?: string
lazyRender?: boolean
customClass?: string
// 定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
enterClass?: string
// 定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
enterActiveClass?: string
// 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 enter-class 被移除),在过渡/动画完成之后移除。
enterToClass?: string
// 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
leaveClass?: string
// 定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
leaveActiveClass?: string
// 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 leave-class 被删除),在过渡/动画完成之后移除。
leaveToClass?: string
}
const props = withDefaults(defineProps<Props>(), {
customClass: '',
customStyle: '',
show: false,
name: 'fade',
duration: 300,
lazyRender: true
})
// 初始化是否完成
const inited = ref<boolean>(false)
// 是否显示
const display = ref<boolean>(false)
// 当前动画状态
const status = ref<string>('')
// 动画是否结束
const transitionEnded = ref<boolean>(false)
// 动画持续时间
const currentDuration = ref<number>(300)
// 类名
const classes = ref<string>('')
const emit = defineEmits(['click', 'before-enter', 'enter', 'before-leave', 'leave', 'after-leave', 'after-enter'])
const style = computed(() => {
return `-webkit-transition-duration:${currentDuration.value}ms;transition-duration:${currentDuration.value}ms;${
display.value ? '' : 'display: none;'
}${props.customStyle}`
})
const rootClass = computed(() => {
return `wd-transition ${props.customClass} ${classes.value}`
})
onBeforeMount(() => {
if (props.show) {
enter()
}
})
watch(
() => props.show,
(newVal) => {
observerShow(newVal)
},
{ deep: true, immediate: true }
)
function handleClick() {
emit('click')
}
function observerShow(value: boolean) {
value ? enter() : leave()
}
function enter() {
const classNames = getClassNames(props.name)
const duration = isObj(props.duration) ? (props.duration as any).enter : props.duration
status.value = 'enter'
emit('before-enter')
requestAnimationFrame(() => {
emit('enter')
classes.value = classNames.enter
currentDuration.value = duration
requestAnimationFrame(() => {
inited.value = true
display.value = true
requestAnimationFrame(() => {
transitionEnded.value = false
classes.value = classNames['enter-to']
})
})
})
}
function leave() {
if (!display.value) return
const classNames = getClassNames(props.name)
const duration = isObj(props.duration) ? (props.duration as any).leave : props.duration
status.value = 'leave'
emit('before-leave')
requestAnimationFrame(() => {
emit('leave')
classes.value = classNames.leave
currentDuration.value = duration
requestAnimationFrame(() => {
transitionEnded.value = false
setTimeout(() => onTransitionEnd(), currentDuration.value)
classes.value = classNames['leave-to']
})
})
}
function onTransitionEnd() {
if (transitionEnded.value) return
transitionEnded.value = true
if (status.value === 'leave') {
// 离开后触发
emit('after-leave')
} else if (status.value === 'enter') {
// 进入后触发
emit('after-enter')
}
if (!props.show && display.value) {
display.value = false
}
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>