useElementScroll.ts 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { useScroll, useThrottleFn } from '@vueuse/core'
  3. import { whenever } from '@vueuse/shared'
  4. import { computed, type ComputedRef, isRef, ref, watch } from 'vue'
  5. import type { MaybeRef } from '@vueuse/shared'
  6. interface Options {
  7. scrollStartThreshold?: ComputedRef<number | undefined>
  8. }
  9. export const useElementScroll = (
  10. scrollContainerElement: MaybeRef<HTMLElement>,
  11. options?: Options,
  12. ) => {
  13. const { y, directions } = useScroll(scrollContainerElement, {
  14. eventListenerOptions: { passive: true },
  15. })
  16. const isScrollingDown = ref(false)
  17. const isScrollingUp = ref(false)
  18. const resetScrolls = () => {
  19. isScrollingDown.value = false
  20. isScrollingUp.value = false
  21. }
  22. const reachedTop = computed(() => y.value === 0)
  23. const scrollNode = computed(() =>
  24. isRef(scrollContainerElement)
  25. ? scrollContainerElement.value
  26. : scrollContainerElement,
  27. )
  28. const reachedBottom = computed(
  29. () => y.value === scrollNode.value.clientHeight,
  30. )
  31. const isScrollable = computed(
  32. () => scrollNode.value?.scrollHeight > scrollNode.value?.clientHeight,
  33. )
  34. const hasReachedThreshold = computed(
  35. () => y.value > (options?.scrollStartThreshold?.value || 0),
  36. )
  37. const omitValueChanges = computed(() => {
  38. return !hasReachedThreshold.value || !isScrollable.value || reachedTop.value
  39. })
  40. whenever(reachedTop, resetScrolls, { flush: 'post' })
  41. const throttledFn = useThrottleFn((newY, oldY) => {
  42. if (omitValueChanges.value) return
  43. if (hasReachedThreshold.value) {
  44. resetScrolls()
  45. }
  46. if (newY > oldY) {
  47. isScrollingDown.value = true
  48. isScrollingUp.value = false
  49. }
  50. if (newY < oldY) {
  51. isScrollingDown.value = false
  52. isScrollingUp.value = true
  53. }
  54. }, 500) // avoid scrolling glitch
  55. watch(y, throttledFn, { flush: 'post' })
  56. return {
  57. y,
  58. directions,
  59. reachedTop,
  60. reachedBottom,
  61. isScrollingDown,
  62. isScrollingUp,
  63. isScrollable,
  64. }
  65. }