ellipsis.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import {
  2. ref,
  3. getCurrentInstance,
  4. onMounted,
  5. toValue,
  6. computed,
  7. watch,
  8. onBeforeUnmount,
  9. onUnmounted,
  10. useSlots,
  11. reactive,
  12. Text,
  13. Comment
  14. } from 'vue'
  15. export function useMounted() {
  16. const isMounted = ref(false)
  17. // 获取当前组件的实例
  18. const instance = getCurrentInstance()
  19. if (instance) {
  20. onMounted(() => {
  21. isMounted.value = true
  22. }, instance)
  23. }
  24. return isMounted
  25. }
  26. export function useSupported(callback) {
  27. const isMounted = useMounted()
  28. return computed(() => {
  29. // to trigger the ref
  30. isMounted.value
  31. return Boolean(callback())
  32. })
  33. }
  34. export function useSlotsExist(slotsName = 'default') {
  35. const slots = useSlots() // 获取当前组件的所有插槽
  36. // 检查特定名称的插槽是否存在且不为空
  37. const checkSlotsExist = (slotName) => {
  38. const slotsContent = slots[slotName]?.()
  39. const checkExist = (slotContent) => {
  40. if (slotContent.type === Comment) {
  41. return false
  42. }
  43. if (Array.isArray(slotContent.children) && !slotContent.children.length) {
  44. return false
  45. }
  46. if (slotContent.type !== Text) {
  47. return true
  48. }
  49. if (typeof slotContent.children === 'string') {
  50. return slotContent.children.trim() !== ''
  51. }
  52. }
  53. if (slotsContent && slotsContent?.length) {
  54. const result = slotsContent.some((slotContent) => {
  55. return checkExist(slotContent)
  56. })
  57. return result
  58. }
  59. return false
  60. }
  61. if (Array.isArray(slotsName)) {
  62. const slotsExist = reactive({})
  63. slotsName.forEach((slotName) => {
  64. const exist = computed(() => checkSlotsExist(slotName))
  65. slotsExist[slotName] = exist // 将一个 ref 赋值给一个 reactive 属性时,该 ref 会自动解包
  66. })
  67. return slotsExist
  68. } else {
  69. return computed(() => checkSlotsExist(slotsName))
  70. }
  71. }
  72. export function useMutationObserver(
  73. target,
  74. callback,
  75. options = {}
  76. ) {
  77. const isSupported = useSupported(() => window && 'MutationObserver' in window)
  78. const stopObservation = ref(false)
  79. let observer
  80. const targets = computed(() => {
  81. const targetsValue = toValue(target)
  82. if (targetsValue) {
  83. if (Array.isArray(targetsValue)) {
  84. return targetsValue.map((el) => toValue(el)).filter((el) => el)
  85. } else {
  86. return [targetsValue]
  87. }
  88. }
  89. return []
  90. })
  91. // 定义清理函数,用于断开 MutationObserver 的连接
  92. const cleanup = () => {
  93. if (observer) {
  94. observer.disconnect()
  95. observer = undefined
  96. }
  97. }
  98. // 初始化 MutationObserver,开始观察目标元素
  99. const observeElements = () => {
  100. if (isSupported.value && targets.value.length && !stopObservation.value) {
  101. observer = new MutationObserver(callback)
  102. if (observer?.observe) {
  103. targets.value.forEach((element) => observer.observe(element, options))
  104. }
  105. }
  106. }
  107. // 监听 targets 的变化,当 targets 变化时,重新建立 MutationObserver 观察
  108. watch(
  109. () => targets.value,
  110. () => {
  111. cleanup()
  112. observeElements()
  113. },
  114. {
  115. immediate: true, // 立即触发回调,以便初始状态也被观察
  116. flush: 'post'
  117. }
  118. )
  119. const stop = () => {
  120. stopObservation.value = true
  121. cleanup()
  122. }
  123. const start = () => {
  124. stopObservation.value = false
  125. observeElements()
  126. }
  127. // 在组件卸载前清理 MutationObserver
  128. onBeforeUnmount(() => cleanup())
  129. return {
  130. stop,
  131. start
  132. }
  133. }
  134. export function useEventListener(target, event, callback) {
  135. // 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
  136. onMounted(() => target.addEventListener(event, callback))
  137. onUnmounted(() => target.removeEventListener(event, callback))
  138. }
  139. export function useResizeObserver(
  140. target,
  141. callback,
  142. options = {}
  143. ) {
  144. const isSupported = useSupported(() => window && 'ResizeObserver' in window)
  145. let observer
  146. const stopObservation = ref(false)
  147. const targets = computed(() => {
  148. const targetsValue = toValue(target)
  149. if (targetsValue) {
  150. if (Array.isArray(targetsValue)) {
  151. return targetsValue.map((el) => toValue(el)).filter((el) => el)
  152. } else {
  153. return [targetsValue]
  154. }
  155. }
  156. return []
  157. })
  158. // 定义清理函数,用于断开 ResizeObserver 的连接
  159. const cleanup = () => {
  160. if (observer) {
  161. observer.disconnect()
  162. observer = undefined
  163. }
  164. }
  165. // 初始化 ResizeObserver,开始观察目标元素
  166. const observeElements = () => {
  167. if (isSupported.value && targets.value.length && !stopObservation.value) {
  168. observer = new ResizeObserver(callback)
  169. if (observer?.observe) {
  170. targets.value.forEach((element) => observer.observe(element, options))
  171. }
  172. }
  173. }
  174. // 监听 targets 的变化,当 targets 变化时,重新建立 ResizeObserver 观察
  175. watch(
  176. () => targets.value,
  177. () => {
  178. cleanup()
  179. observeElements()
  180. },
  181. {
  182. immediate: true, // 立即触发回调,以便初始状态也被观察
  183. flush: 'post'
  184. }
  185. )
  186. const stop = () => {
  187. stopObservation.value = true
  188. cleanup()
  189. }
  190. const start = () => {
  191. stopObservation.value = false
  192. observeElements()
  193. }
  194. // 在组件卸载前清理 ResizeObserver
  195. onBeforeUnmount(() => cleanup())
  196. return {
  197. stop,
  198. start
  199. }
  200. }
  201. export function rafTimeout(fn, delay, interval = false) {
  202. let start = null // 记录动画开始的时间戳
  203. function timeElapse(timestamp) {
  204. // 定义动画帧回调函数
  205. /*
  206. timestamp参数:与 performance.now() 的返回值相同,它表示 requestAnimationFrame() 开始去执行回调函数的时刻
  207. */
  208. if (!start) {
  209. // 如果还没有开始时间,则以当前时间为开始时间
  210. start = timestamp
  211. }
  212. const elapsed = timestamp - start
  213. if (elapsed >= delay) {
  214. try {
  215. fn() // 执行目标函数
  216. } catch (error) {
  217. console.error('Error executing rafTimeout function:', error)
  218. }
  219. if (interval) {
  220. // 如果需要间隔执行,则重置开始时间并继续安排下一次动画帧
  221. start = timestamp
  222. raf.id = requestAnimationFrame(timeElapse)
  223. }
  224. } else {
  225. raf.id = requestAnimationFrame(timeElapse)
  226. }
  227. }
  228. // 创建一个对象用于存储动画帧的 ID,并初始化动画帧
  229. const raf = {
  230. id: requestAnimationFrame(timeElapse)
  231. }
  232. return raf
  233. }
  234. export function cancelRaf(raf = { id }) {
  235. if (raf && raf.id && typeof raf.id === 'number') {
  236. cancelAnimationFrame(raf.id)
  237. } else {
  238. console.warn('cancelRaf received an invalid id:', raf)
  239. }
  240. }