96 lines
3.4 KiB
JavaScript
96 lines
3.4 KiB
JavaScript
|
import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue";
|
||
|
// Styles
|
||
|
import "./VParallax.css";
|
||
|
|
||
|
// Components
|
||
|
import { VImg } from "../VImg/index.mjs"; // Composables
|
||
|
import { useDisplay } from "../../composables/index.mjs";
|
||
|
import { makeComponentProps } from "../../composables/component.mjs";
|
||
|
import { useIntersectionObserver } from "../../composables/intersectionObserver.mjs";
|
||
|
import { useResizeObserver } from "../../composables/resizeObserver.mjs"; // Utilities
|
||
|
import { computed, onBeforeUnmount, ref, watch, watchEffect } from 'vue';
|
||
|
import { clamp, genericComponent, getScrollParent, propsFactory, useRender } from "../../util/index.mjs"; // Types
|
||
|
function floor(val) {
|
||
|
return Math.floor(Math.abs(val)) * Math.sign(val);
|
||
|
}
|
||
|
export const makeVParallaxProps = propsFactory({
|
||
|
scale: {
|
||
|
type: [Number, String],
|
||
|
default: 0.5
|
||
|
},
|
||
|
...makeComponentProps()
|
||
|
}, 'VParallax');
|
||
|
export const VParallax = genericComponent()({
|
||
|
name: 'VParallax',
|
||
|
props: makeVParallaxProps(),
|
||
|
setup(props, _ref) {
|
||
|
let {
|
||
|
slots
|
||
|
} = _ref;
|
||
|
const {
|
||
|
intersectionRef,
|
||
|
isIntersecting
|
||
|
} = useIntersectionObserver();
|
||
|
const {
|
||
|
resizeRef,
|
||
|
contentRect
|
||
|
} = useResizeObserver();
|
||
|
const {
|
||
|
height: displayHeight
|
||
|
} = useDisplay();
|
||
|
const root = ref();
|
||
|
watchEffect(() => {
|
||
|
intersectionRef.value = resizeRef.value = root.value?.$el;
|
||
|
});
|
||
|
let scrollParent;
|
||
|
watch(isIntersecting, val => {
|
||
|
if (val) {
|
||
|
scrollParent = getScrollParent(intersectionRef.value);
|
||
|
scrollParent = scrollParent === document.scrollingElement ? document : scrollParent;
|
||
|
scrollParent.addEventListener('scroll', onScroll, {
|
||
|
passive: true
|
||
|
});
|
||
|
onScroll();
|
||
|
} else {
|
||
|
scrollParent.removeEventListener('scroll', onScroll);
|
||
|
}
|
||
|
});
|
||
|
onBeforeUnmount(() => {
|
||
|
scrollParent?.removeEventListener('scroll', onScroll);
|
||
|
});
|
||
|
watch(displayHeight, onScroll);
|
||
|
watch(() => contentRect.value?.height, onScroll);
|
||
|
const scale = computed(() => {
|
||
|
return 1 - clamp(+props.scale);
|
||
|
});
|
||
|
let frame = -1;
|
||
|
function onScroll() {
|
||
|
if (!isIntersecting.value) return;
|
||
|
cancelAnimationFrame(frame);
|
||
|
frame = requestAnimationFrame(() => {
|
||
|
const el = (root.value?.$el).querySelector('.v-img__img');
|
||
|
if (!el) return;
|
||
|
const scrollHeight = scrollParent instanceof Document ? document.documentElement.clientHeight : scrollParent.clientHeight;
|
||
|
const scrollPos = scrollParent instanceof Document ? window.scrollY : scrollParent.scrollTop;
|
||
|
const top = intersectionRef.value.getBoundingClientRect().top + scrollPos;
|
||
|
const height = contentRect.value.height;
|
||
|
const center = top + (height - scrollHeight) / 2;
|
||
|
const translate = floor((scrollPos - center) * scale.value);
|
||
|
const sizeScale = Math.max(1, (scale.value * (scrollHeight - height) + height) / height);
|
||
|
el.style.setProperty('transform', `translateY(${translate}px) scale(${sizeScale})`);
|
||
|
});
|
||
|
}
|
||
|
useRender(() => _createVNode(VImg, {
|
||
|
"class": ['v-parallax', {
|
||
|
'v-parallax--active': isIntersecting.value
|
||
|
}, props.class],
|
||
|
"style": props.style,
|
||
|
"ref": root,
|
||
|
"cover": true,
|
||
|
"onLoadstart": onScroll,
|
||
|
"onLoad": onScroll
|
||
|
}, slots));
|
||
|
return {};
|
||
|
}
|
||
|
});
|
||
|
//# sourceMappingURL=VParallax.mjs.map
|