@neeravp/vue-3-animate-in-view
Version:
Vue3 Directive & Component to animate elements as they appear in viewport
210 lines (171 loc) • 5.57 kB
JavaScript
import { computed, watch, defineComponent, ref, onMounted, onBeforeUnmount, pushScopeId, popScopeId, openBlock, createBlock, renderSlot, withScopeId } from 'vue';
const intersectionHandler = (isIntersectingFn, isInView) => function handleIntersection(entries) {
entries.forEach(entry => {
isIntersectingFn(entry, isInView);
});
};
function observeElement(element, isInView, isIntersectingFn, options) {
console.log(element);
const handleIntersection = intersectionHandler(isIntersectingFn, isInView);
const observer = new IntersectionObserver(handleIntersection, options);
observer.observe(element);
}
var useIntersectionObserver = {
observeElement
};
let handler;
function detectScrollDirection(scrollDetectionHandler) {
let lastScrollTop = window.pageYOffset;
let ticking = false;
handler = () => {
const currentScrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (!ticking) {
window.requestAnimationFrame(() => {
// execute scrollDetectionHandler
scrollDetectionHandler(lastScrollTop, currentScrollTop);
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
ticking = false;
});
ticking = true;
}
};
window.addEventListener("scroll", handler);
}
function removeEventListener() {
window.removeEventListener("scroll", handler);
}
var useScrollObserver = {
detectScrollDirection,
removeEventListener
};
function apply(el, animation, repeat, isInView, scrollDirection) {
// console.log(targetElement, propAnimation, propsRepeat)
const animationClass = computed(() => {
let _animationClass = '';
if (typeof animation === 'string') {
_animationClass = animation;
}
if (typeof animation === 'object') {
_animationClass = scrollDirection.value === 'down' ? animation.down : animation.up;
}
return _animationClass;
});
const scrollCallback = (lastScrollTop, currentScrollTop) => {
scrollDirection.value = currentScrollTop < lastScrollTop ? 'up' : 'down';
lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
};
const intersectCallback = (entry, isInView) => {
if (entry.isIntersecting) {
el.classList.add(animationClass.value);
isInView.value = true;
} else {
// el.classList.remove(animationClass.value)
isInView.value = false;
}
};
const isDirectionAgnostic = () => {
return typeof animation === 'string';
};
const isBiDirectional = () => {
return !!(typeof animation === 'object' && animation.up !== '' && animation.down !== '');
};
useIntersectionObserver.observeElement(el, isInView, intersectCallback);
useScrollObserver.detectScrollDirection(scrollCallback);
watch([isInView, scrollDirection], (newValues, previousValues) => {
const [isInView, scrollDirection] = newValues;
if (!repeat && isDirectionAgnostic()) {
console.log('No Repeat & isDirectionAgnostic: true');
return;
} else if (!isInView) {
console.log('Not in view');
el.classList.remove(animationClass.value);
} else if (isBiDirectional()) {
console.log('isBiDirectional');
animation = animation;
if (scrollDirection === 'up') {
el.classList.remove(animation.down);
el.classList.add(animationClass.value);
}
if (scrollDirection === 'down') {
el.classList.remove(animation.up);
el.classList.add(animationClass.value);
}
}
});
}
function cleanup() {
useScrollObserver.removeEventListener();
}
var useAnimateInView = {
apply,
cleanup
};
var script = defineComponent({
name: 'AnimateInView',
props: {
threshold: {
type: Number,
default: 10
},
animation: {
type: [String, Object],
default: "fadeInSlide"
},
repeat: {
type: Boolean,
default: false
}
},
setup(props) {
const target = ref(null);
const isInView = ref(false);
const scrollDirection = ref('down');
onMounted(() => {
console.log(target.value);
useAnimateInView.apply(target.value, props.animation, props.repeat, isInView, scrollDirection);
});
onBeforeUnmount(() => useAnimateInView.cleanup());
return {
target,
isInView,
scrollDirection
};
}
});
const _withId = /*#__PURE__*/withScopeId("data-v-679b8a2b");
pushScopeId("data-v-679b8a2b");
const _hoisted_1 = {
class: "transition-all duration-1000",
ref: "target"
};
popScopeId();
const render = /*#__PURE__*/_withId((_ctx, _cache, $props, $setup, $data, $options) => {
return openBlock(), createBlock("div", _hoisted_1, [renderSlot(_ctx.$slots, "default")], 512
/* NEED_PATCH */
);
});
script.render = render;
script.__scopeId = "data-v-679b8a2b";
script.__file = "src/components/AnimateInView.vue";
const animateInView = {
mounted(el, binding) {
console.log(binding);
const isInView = ref(false);
const scrollDirection = ref('down');
const animation = binding.value;
const repeat = binding.modifiers.repeat || false;
useAnimateInView.apply(el, animation, repeat, isInView, scrollDirection);
},
beforeUnmount() {
useAnimateInView.cleanup();
}
};
const vue3AnimateInView = {
install: (app, options) => {
app.directive('animate-inview', animateInView);
app.component('animate-in-view', script);
}
};
// export { default as AnimateInViewDirective } from '@/directives/AnimateInView'
export default vue3AnimateInView;
export { script as AnimateInViewComponent, animateInView as AnimateInViewDirective };