scroll.mjs 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. // Utilities
  2. import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
  3. import { clamp, consoleWarn, propsFactory } from "../util/index.mjs"; // Types
  4. // Composables
  5. export const makeScrollProps = propsFactory({
  6. scrollTarget: {
  7. type: String
  8. },
  9. scrollThreshold: {
  10. type: [String, Number],
  11. default: 300
  12. }
  13. }, 'scroll');
  14. export function useScroll(props) {
  15. let args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  16. const {
  17. canScroll
  18. } = args;
  19. let previousScroll = 0;
  20. let previousScrollHeight = 0;
  21. const target = ref(null);
  22. const currentScroll = shallowRef(0);
  23. const savedScroll = shallowRef(0);
  24. const currentThreshold = shallowRef(0);
  25. const isScrollActive = shallowRef(false);
  26. const isScrollingUp = shallowRef(false);
  27. const scrollThreshold = computed(() => {
  28. return Number(props.scrollThreshold);
  29. });
  30. /**
  31. * 1: at top
  32. * 0: at threshold
  33. */
  34. const scrollRatio = computed(() => {
  35. return clamp((scrollThreshold.value - currentScroll.value) / scrollThreshold.value || 0);
  36. });
  37. const onScroll = () => {
  38. const targetEl = target.value;
  39. if (!targetEl || canScroll && !canScroll.value) return;
  40. previousScroll = currentScroll.value;
  41. currentScroll.value = 'window' in targetEl ? targetEl.pageYOffset : targetEl.scrollTop;
  42. const currentScrollHeight = targetEl instanceof Window ? document.documentElement.scrollHeight : targetEl.scrollHeight;
  43. if (previousScrollHeight !== currentScrollHeight) {
  44. previousScrollHeight = currentScrollHeight;
  45. return;
  46. }
  47. isScrollingUp.value = currentScroll.value < previousScroll;
  48. currentThreshold.value = Math.abs(currentScroll.value - scrollThreshold.value);
  49. };
  50. watch(isScrollingUp, () => {
  51. savedScroll.value = savedScroll.value || currentScroll.value;
  52. });
  53. watch(isScrollActive, () => {
  54. savedScroll.value = 0;
  55. });
  56. onMounted(() => {
  57. watch(() => props.scrollTarget, scrollTarget => {
  58. const newTarget = scrollTarget ? document.querySelector(scrollTarget) : window;
  59. if (!newTarget) {
  60. consoleWarn(`Unable to locate element with identifier ${scrollTarget}`);
  61. return;
  62. }
  63. if (newTarget === target.value) return;
  64. target.value?.removeEventListener('scroll', onScroll);
  65. target.value = newTarget;
  66. target.value.addEventListener('scroll', onScroll, {
  67. passive: true
  68. });
  69. }, {
  70. immediate: true
  71. });
  72. });
  73. onBeforeUnmount(() => {
  74. target.value?.removeEventListener('scroll', onScroll);
  75. });
  76. // Do we need this? If yes - seems that
  77. // there's no need to expose onScroll
  78. canScroll && watch(canScroll, onScroll, {
  79. immediate: true
  80. });
  81. return {
  82. scrollThreshold,
  83. currentScroll,
  84. currentThreshold,
  85. isScrollActive,
  86. scrollRatio,
  87. // required only for testing
  88. // probably can be removed
  89. // later (2 chars chlng)
  90. isScrollingUp,
  91. savedScroll
  92. };
  93. }
  94. //# sourceMappingURL=scroll.mjs.map