wd-transition.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <template>
  2. <view
  3. :class="rootClass"
  4. :style="style"
  5. @transitionend="onTransitionEnd"
  6. @click="handleClick"
  7. @touchmove.stop.prevent="noop"
  8. v-if="isShow && disableTouchMove"
  9. >
  10. <slot />
  11. </view>
  12. <view :class="rootClass" :style="style" @transitionend="onTransitionEnd" @click="handleClick" v-else-if="isShow && !disableTouchMove">
  13. <slot />
  14. </view>
  15. </template>
  16. <script lang="ts">
  17. export default {
  18. name: 'wd-transition',
  19. options: {
  20. addGlobalClass: true,
  21. virtualHost: true,
  22. styleIsolation: 'shared'
  23. }
  24. }
  25. </script>
  26. <script lang="ts" setup>
  27. import { computed, onBeforeMount, ref, watch } from 'vue'
  28. import { isObj, isPromise, pause } from '../common/util'
  29. import { transitionProps, type TransitionName } from './types'
  30. import { AbortablePromise } from '../common/AbortablePromise'
  31. const getClassNames = (name?: TransitionName | TransitionName[]) => {
  32. let enter: string = `${props.enterClass} ${props.enterActiveClass}`
  33. let enterTo: string = `${props.enterToClass} ${props.enterActiveClass}`
  34. let leave: string = `${props.leaveClass} ${props.leaveActiveClass}`
  35. let leaveTo: string = `${props.leaveToClass} ${props.leaveActiveClass}`
  36. if (Array.isArray(name)) {
  37. for (let index = 0; index < name.length; index++) {
  38. enter = `wd-${name[index]}-enter wd-${name[index]}-enter-active ${enter}`
  39. enterTo = `wd-${name[index]}-enter-to wd-${name[index]}-enter-active ${enterTo}`
  40. leave = `wd-${name[index]}-leave wd-${name[index]}-leave-active ${leave}`
  41. leaveTo = `wd-${name[index]}-leave-to wd-${name[index]}-leave-active ${leaveTo}`
  42. }
  43. } else if (name) {
  44. enter = `wd-${name}-enter wd-${name}-enter-active ${enter}`
  45. enterTo = `wd-${name}-enter-to wd-${name}-enter-active ${enterTo}`
  46. leave = `wd-${name}-leave wd-${name}-leave-active ${leave}`
  47. leaveTo = `wd-${name}-leave-to wd-${name}-leave-active ${leaveTo}`
  48. }
  49. return {
  50. enter: enter,
  51. 'enter-to': enterTo,
  52. leave: leave,
  53. 'leave-to': leaveTo
  54. }
  55. }
  56. const props = defineProps(transitionProps)
  57. const emit = defineEmits(['click', 'before-enter', 'enter', 'before-leave', 'leave', 'after-leave', 'after-enter'])
  58. // 初始化是否完成
  59. const inited = ref<boolean>(false)
  60. // 是否显示
  61. const display = ref<boolean>(false)
  62. // 当前动画状态
  63. const status = ref<string>('')
  64. // 动画是否结束
  65. const transitionEnded = ref<boolean>(false)
  66. // 动画持续时间
  67. const currentDuration = ref<number>(300)
  68. // 类名
  69. const classes = ref<string>('')
  70. // 用于控制enter和leave的顺序执行
  71. const enterPromise = ref<AbortablePromise<void> | null>(null)
  72. // 动画进入的生命周期
  73. const enterLifeCyclePromises = ref<AbortablePromise<unknown> | null>(null)
  74. // 动画离开的生命周期
  75. const leaveLifeCyclePromises = ref<AbortablePromise<unknown> | null>(null)
  76. const style = computed(() => {
  77. return `-webkit-transition-duration:${currentDuration.value}ms;transition-duration:${currentDuration.value}ms;${
  78. display.value || !props.destroy ? '' : 'display: none;'
  79. }${props.customStyle}`
  80. })
  81. const rootClass = computed(() => {
  82. return `wd-transition ${props.customClass} ${classes.value}`
  83. })
  84. const isShow = computed(() => {
  85. return !props.lazyRender || inited.value
  86. })
  87. onBeforeMount(() => {
  88. if (props.show) {
  89. enter()
  90. }
  91. })
  92. watch(
  93. () => props.show,
  94. (newVal) => {
  95. handleShow(newVal)
  96. },
  97. { deep: true }
  98. )
  99. function handleClick() {
  100. emit('click')
  101. }
  102. function handleShow(value: boolean) {
  103. if (value) {
  104. handleAbortPromise()
  105. enter()
  106. } else {
  107. leave()
  108. }
  109. }
  110. /**
  111. * 取消所有的promise
  112. */
  113. function handleAbortPromise() {
  114. isPromise(enterPromise.value) && enterPromise.value.abort()
  115. isPromise(enterLifeCyclePromises.value) && enterLifeCyclePromises.value.abort()
  116. isPromise(leaveLifeCyclePromises.value) && leaveLifeCyclePromises.value.abort()
  117. enterPromise.value = null
  118. enterLifeCyclePromises.value = null
  119. leaveLifeCyclePromises.value = null
  120. }
  121. function enter() {
  122. enterPromise.value = new AbortablePromise(async (resolve) => {
  123. try {
  124. const classNames = getClassNames(props.name)
  125. const duration = isObj(props.duration) ? (props.duration as any).enter : props.duration
  126. status.value = 'enter'
  127. emit('before-enter')
  128. enterLifeCyclePromises.value = pause()
  129. await enterLifeCyclePromises.value
  130. emit('enter')
  131. classes.value = classNames.enter
  132. currentDuration.value = duration
  133. enterLifeCyclePromises.value = pause()
  134. await enterLifeCyclePromises.value
  135. inited.value = true
  136. display.value = true
  137. enterLifeCyclePromises.value = pause()
  138. await enterLifeCyclePromises.value
  139. enterLifeCyclePromises.value = null
  140. transitionEnded.value = false
  141. classes.value = classNames['enter-to']
  142. resolve()
  143. } catch (error) {
  144. /**
  145. *
  146. */
  147. }
  148. })
  149. }
  150. async function leave() {
  151. if (!enterPromise.value) {
  152. transitionEnded.value = false
  153. return onTransitionEnd()
  154. }
  155. try {
  156. await enterPromise.value
  157. if (!display.value) return
  158. const classNames = getClassNames(props.name)
  159. const duration = isObj(props.duration) ? (props.duration as any).leave : props.duration
  160. status.value = 'leave'
  161. emit('before-leave')
  162. currentDuration.value = duration
  163. leaveLifeCyclePromises.value = pause()
  164. await leaveLifeCyclePromises.value
  165. emit('leave')
  166. classes.value = classNames.leave
  167. leaveLifeCyclePromises.value = pause()
  168. await leaveLifeCyclePromises.value
  169. transitionEnded.value = false
  170. classes.value = classNames['leave-to']
  171. leaveLifeCyclePromises.value = setPromise(currentDuration.value)
  172. await leaveLifeCyclePromises.value
  173. leaveLifeCyclePromises.value = null
  174. onTransitionEnd()
  175. enterPromise.value = null
  176. } catch (error) {
  177. /**
  178. *
  179. */
  180. }
  181. }
  182. /**
  183. * 定时器promise化
  184. * @param duration 持续时间ms
  185. */
  186. function setPromise(duration: number) {
  187. return new AbortablePromise<void>((resolve) => {
  188. const timer = setTimeout(() => {
  189. clearTimeout(timer)
  190. resolve()
  191. }, duration)
  192. })
  193. }
  194. function onTransitionEnd() {
  195. if (transitionEnded.value) return
  196. transitionEnded.value = true
  197. if (status.value === 'leave') {
  198. // 离开后触发
  199. emit('after-leave')
  200. } else if (status.value === 'enter') {
  201. // 进入后触发
  202. emit('after-enter')
  203. }
  204. if (!props.show && display.value) {
  205. display.value = false
  206. }
  207. }
  208. function noop() {}
  209. </script>
  210. <style lang="scss" scoped>
  211. @import './index.scss';
  212. </style>