index.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // @ts-nocheck
  2. import { raf } from '@/uni_modules/lime-shared/raf';
  3. // #ifndef UNI-APP-X
  4. import { watchEffect, ref, nextTick } from '@/uni_modules/lime-shared/vue'
  5. // #endif
  6. export type TransitionEmitStatus = "before-enter" | "enter" | "after-enter" | "before-leave" | "leave" | "after-leave"
  7. export type TransitionStatus = '' | 'enter' | 'leave';
  8. export type UseTransitionOptions = {
  9. element ?: Ref<UniElement | null>,
  10. enterClass ?: string,
  11. enterActiveClass ?: string,
  12. enterToClass ?: string,
  13. leaveClass ?: string,
  14. leaveActiveClass ?: string,
  15. leaveToClass ?: string,
  16. appear ?: boolean,
  17. defaultName ?: string,
  18. name ?: () => string,
  19. visible ?: () => boolean,
  20. emits ?: (name : TransitionEmitStatus) => void,
  21. onNextTick ?: (name : TransitionEmitStatus) => Promise<void>,
  22. duration ?: number
  23. removeClasses?: boolean;
  24. }
  25. type ClassNameMap = Map<string, string>;
  26. export type UseTransitionReturn = {
  27. state : Ref<boolean>,
  28. display : Ref<boolean>,
  29. inited : Ref<boolean>,
  30. classes : Ref<string>,
  31. name : Ref<string>,
  32. finished : () => void,
  33. toggle : (v : boolean) => void,
  34. }
  35. export function useTransition(options : UseTransitionOptions) : UseTransitionReturn {
  36. const state = ref(false);
  37. const display = ref(false);
  38. const inited = ref(false);
  39. const classes = ref('');
  40. const name = ref(options.defaultName ?? 'fade');
  41. const enterClass = options.enterClass ?? '';
  42. const enterActiveClass = options.enterActiveClass ?? '';
  43. const enterToClass = options.enterToClass ?? '';
  44. const leaveActiveClass = options.leaveActiveClass ?? '';
  45. const leaveToClass = options.leaveToClass ?? '';
  46. const leaveClass = options.leaveClass ?? '';
  47. const appear = options.appear ?? false
  48. const duration = options.duration ?? 300;
  49. let status : TransitionStatus = '';
  50. let isTransitionEnd = false;
  51. let isTransitioning = false;
  52. let timeoutId = -1
  53. const emitEvent = (event : TransitionEmitStatus) => {
  54. options.emits?.(event);
  55. };
  56. // 结束
  57. const finished = () => {
  58. if (isTransitionEnd) return;
  59. isTransitionEnd = true;
  60. if (options.removeClasses ?? false) {
  61. classes.value = '';
  62. }
  63. emitEvent(`after-${status}`)
  64. // classes.value = ''
  65. if (display.value && !state.value) {
  66. display.value = false
  67. }
  68. }
  69. const sleep = () : Promise<void> => {
  70. return new Promise((resolve) => {
  71. nextTick(() => {
  72. raf(() => {
  73. // #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
  74. if (options.element?.value != null) {
  75. options.element?.value?.getBoundingClientRectAsync()?.then(res => {
  76. resolve()
  77. })
  78. } else {
  79. resolve()
  80. }
  81. // #endif
  82. // #ifndef APP-ANDROID || APP-IOS || APP-HARMONY
  83. resolve()
  84. // #endif
  85. })
  86. })
  87. })
  88. }
  89. const getClassNames = (name : string) : ClassNameMap => {
  90. return new Map<string, string>([
  91. ['enter', `l-${name}-enter l-${name}-enter-active ${enterClass} ${enterActiveClass}`],
  92. ['enter-to', `l-${name}-enter-to l-${name}-enter-active ${enterToClass} ${enterActiveClass}`],
  93. ['leave', `l-${name}-leave l-${name}-leave-active ${leaveClass} ${leaveActiveClass}`],
  94. ['leave-to', `l-${name}-leave-to l-${name}-leave-active ${leaveToClass} ${leaveActiveClass}`]
  95. ])
  96. };
  97. const transitionQueue = ref<TransitionStatus[]>([]);
  98. const performTransition = async (newStatus : TransitionStatus, eventName : TransitionStatus) => {
  99. if (status == newStatus) return
  100. transitionQueue.value.push(newStatus);
  101. if (isTransitioning) return;
  102. isTransitioning = true;
  103. // 防止因结束 又切换为开始导致闪烁
  104. isTransitionEnd = true;
  105. while (transitionQueue.value.length > 0) {
  106. const currentStatus = transitionQueue.value.shift()!;
  107. status = currentStatus;
  108. emitEvent(`before-${eventName}`);
  109. // IOS hbx4.76如果时间过短会卡住
  110. await sleep();
  111. await sleep();
  112. await sleep();
  113. await sleep();
  114. await sleep();
  115. if (status != currentStatus) continue;
  116. const classNames = getClassNames(name.value);
  117. inited.value = true;
  118. display.value = true;
  119. // const executeBeforeTick = options.onNextTick?.(`before-${eventName}`);
  120. // if (executeBeforeTick != null) {
  121. // await executeBeforeTick;
  122. // }
  123. classes.value = classNames.get(eventName)!;
  124. emitEvent(eventName);
  125. const executeAfterTick = options.onNextTick?.(eventName);
  126. if (executeAfterTick != null) {
  127. await executeAfterTick;
  128. }
  129. await sleep();
  130. // #ifdef MP
  131. await sleep();
  132. await sleep();
  133. // #endif
  134. if (status != currentStatus) continue;
  135. classes.value = classNames.get(`${eventName}-to`)!;
  136. // isTransitionEnd = false;
  137. // 防止最后无法关闭
  138. if (status == 'leave') {
  139. setTimeout(() => {
  140. finished()
  141. }, duration)
  142. }
  143. }
  144. // 不延迟 会导致快速切换时 因display none 闪烁
  145. clearTimeout(timeoutId)
  146. timeoutId = setTimeout(() => {
  147. if (transitionQueue.value.length == 0 && status == newStatus) {
  148. isTransitionEnd = false;
  149. }
  150. }, duration * 0.8)
  151. isTransitioning = false;
  152. }
  153. // 定义进入过渡的函数
  154. const enter = () => {
  155. performTransition('enter', 'enter');
  156. }
  157. const leave = () => {
  158. performTransition('leave', 'leave');
  159. }
  160. let init = false;
  161. watchEffect(() => {
  162. if (options.visible == null) return
  163. state.value = options.visible!();
  164. if (!appear && !init) {
  165. init = true
  166. return
  167. }
  168. if (state.value) {
  169. enter()
  170. } else {
  171. leave()
  172. }
  173. })
  174. watchEffect(() => {
  175. if (options.name == null) return
  176. name.value = options.name!()
  177. })
  178. const toggle = (v : boolean) => {
  179. state.value = v
  180. if (v) {
  181. enter()
  182. } else {
  183. leave()
  184. }
  185. }
  186. return {
  187. state,
  188. inited,
  189. display,
  190. classes,
  191. name,
  192. finished,
  193. toggle
  194. } as UseTransitionReturn
  195. }