VParallax.mjs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue";
  2. // Styles
  3. import "./VParallax.css";
  4. // Components
  5. import { VImg } from "../VImg/index.mjs"; // Composables
  6. import { useDisplay } from "../../composables/index.mjs";
  7. import { makeComponentProps } from "../../composables/component.mjs";
  8. import { useIntersectionObserver } from "../../composables/intersectionObserver.mjs";
  9. import { useResizeObserver } from "../../composables/resizeObserver.mjs"; // Utilities
  10. import { computed, onBeforeUnmount, ref, watch, watchEffect } from 'vue';
  11. import { clamp, genericComponent, getScrollParent, propsFactory, useRender } from "../../util/index.mjs"; // Types
  12. function floor(val) {
  13. return Math.floor(Math.abs(val)) * Math.sign(val);
  14. }
  15. export const makeVParallaxProps = propsFactory({
  16. scale: {
  17. type: [Number, String],
  18. default: 0.5
  19. },
  20. ...makeComponentProps()
  21. }, 'VParallax');
  22. export const VParallax = genericComponent()({
  23. name: 'VParallax',
  24. props: makeVParallaxProps(),
  25. setup(props, _ref) {
  26. let {
  27. slots
  28. } = _ref;
  29. const {
  30. intersectionRef,
  31. isIntersecting
  32. } = useIntersectionObserver();
  33. const {
  34. resizeRef,
  35. contentRect
  36. } = useResizeObserver();
  37. const {
  38. height: displayHeight
  39. } = useDisplay();
  40. const root = ref();
  41. watchEffect(() => {
  42. intersectionRef.value = resizeRef.value = root.value?.$el;
  43. });
  44. let scrollParent;
  45. watch(isIntersecting, val => {
  46. if (val) {
  47. scrollParent = getScrollParent(intersectionRef.value);
  48. scrollParent = scrollParent === document.scrollingElement ? document : scrollParent;
  49. scrollParent.addEventListener('scroll', onScroll, {
  50. passive: true
  51. });
  52. onScroll();
  53. } else {
  54. scrollParent.removeEventListener('scroll', onScroll);
  55. }
  56. });
  57. onBeforeUnmount(() => {
  58. scrollParent?.removeEventListener('scroll', onScroll);
  59. });
  60. watch(displayHeight, onScroll);
  61. watch(() => contentRect.value?.height, onScroll);
  62. const scale = computed(() => {
  63. return 1 - clamp(+props.scale);
  64. });
  65. let frame = -1;
  66. function onScroll() {
  67. if (!isIntersecting.value) return;
  68. cancelAnimationFrame(frame);
  69. frame = requestAnimationFrame(() => {
  70. const el = (root.value?.$el).querySelector('.v-img__img');
  71. if (!el) return;
  72. const scrollHeight = scrollParent instanceof Document ? document.documentElement.clientHeight : scrollParent.clientHeight;
  73. const scrollPos = scrollParent instanceof Document ? window.scrollY : scrollParent.scrollTop;
  74. const top = intersectionRef.value.getBoundingClientRect().top + scrollPos;
  75. const height = contentRect.value.height;
  76. const center = top + (height - scrollHeight) / 2;
  77. const translate = floor((scrollPos - center) * scale.value);
  78. const sizeScale = Math.max(1, (scale.value * (scrollHeight - height) + height) / height);
  79. el.style.setProperty('transform', `translateY(${translate}px) scale(${sizeScale})`);
  80. });
  81. }
  82. useRender(() => _createVNode(VImg, {
  83. "class": ['v-parallax', {
  84. 'v-parallax--active': isIntersecting.value
  85. }, props.class],
  86. "style": props.style,
  87. "ref": root,
  88. "cover": true,
  89. "onLoadstart": onScroll,
  90. "onLoad": onScroll
  91. }, slots));
  92. return {};
  93. }
  94. });
  95. //# sourceMappingURL=VParallax.mjs.map