lumecss
Version:
LumeCss is a modern, lightweight, and flexible CSS library designed for building fast and responsive user interfaces. Built with SCSS and TypeScript, LumeCss provides utility-first classes, customizable themes, and reusable components to speed up your dev
114 lines (91 loc) • 5.27 kB
text/typescript
// Declare an array containing the scroll animation classes
const animationClasses: string[] = [
'scroll-fade-up', 'scroll-fade-down', 'scroll-fade-left', 'scroll-fade-right',
'scroll-fade-up-right', 'scroll-fade-up-left', 'scroll-fade-down-right', 'scroll-fade-down-left',
'scroll-flip-left', 'scroll-flip-right', 'scroll-flip-up', 'scroll-flip-down',
'scroll-zoom-in', 'scroll-zoom-in-up', 'scroll-zoom-in-down', 'scroll-zoom-in-left', 'scroll-zoom-in-right',
'scroll-zoom-out', 'scroll-zoom-out-up', 'scroll-zoom-out-down', 'scroll-zoom-out-left', 'scroll-zoom-out-right',
];
// Declare an empty array of scroll animation objects
const animationObjects: HTMLElement[] = [];
/* Iterate over each class in animationClasses and append all
elements with that class to the animationObjects array */
animationClasses.forEach((className: string): void => {
/* Retrieve an array of elements with the specified className and push them into the animationObjects array */
const elements: NodeListOf<HTMLElement> = document.querySelectorAll(`.${className}`);
elements.forEach((el: HTMLElement): number => animationObjects.push(el));
});
// Add a listener for when the page finishes loading
window.addEventListener('load', (): void => {
// On page load, we assume the user is starting at the top and scrolling down
const isScrollingDown = true;
// Loop through each element we're observing for animations
animationObjects.forEach((el: HTMLElement): void => {
// Get the element's position relative to the viewport
const rect: DOMRect = el.getBoundingClientRect();
/*
Determine if the element is already within the visible part of the viewport.
This mimics the IntersectionObserver's threshold of 0.2 (20% visibility).
- If the top of the element is less than 80% of the window height,
we assume it's "intersecting" (since 20% or more is visible).
*/
const inViewport: boolean = rect.top < window.innerHeight * 0.8;
// Retrieve the data-scroll-delay attribute of the element, or null if it does not exist.
const scrollDelay: string | null = el?.getAttribute('data-scroll-delay');
const scrollDelayValue: number | null = scrollDelay ? parseFloat(scrollDelay) : null;
// If it's in the viewport and we're scrolling down (initial page load), add the animation class
if (inViewport && isScrollingDown) {
// Check if the scrollDelayValue is not null
if (scrollDelayValue) {
// Add the in-view class to the element after scrollDelayValue milliseconds
setTimeout((): void => el.classList.add('in-view'), scrollDelayValue);
return;
}
el.classList.add('in-view');
}
});
});
let lastScrollY: number = window.scrollY; // Store the last scroll Y position
// Create a new IntersectionObserver instance to monitor visibility of target elements
const observer = new IntersectionObserver(
(
entries: IntersectionObserverEntry[], // Array of observed entries (elements being watched)
observer: IntersectionObserver // The observer instance itself
): void => {
// Determine current scroll direction
const currentScrollY: number = window.scrollY;
const isScrollingDown: boolean = currentScrollY > lastScrollY;
lastScrollY = currentScrollY;
// Loop through each observed entry
entries.forEach((entry: IntersectionObserverEntry): void => {
const target = entry.target as HTMLElement;
// Retrieve the data-scroll-delay attribute of the target, or null if it does not exist.
const scrollDelay: string | null = target?.getAttribute('data-scroll-delay');
const scrollDelayValue: number | null = scrollDelay ? parseFloat(scrollDelay) : null;
if (entry.isIntersecting && isScrollingDown) {
// Check if the scrollDelayValue is not null
if (scrollDelayValue) {
// Add the in-view class to the element after scrollDelayValue milliseconds
setTimeout((): void => target.classList.add('in-view'), scrollDelayValue);
return;
}
// Add the 'in-view' class ONLY when scrolling down and the element enters the viewport
target.classList.add('in-view');
} else if (!entry.isIntersecting && !isScrollingDown) {
// Check if the scrollDelayValue is not null
if (scrollDelayValue) {
// Add the in-view class to the element after scrollDelayValue milliseconds
setTimeout((): void => target.classList.remove('in-view'), scrollDelayValue);
return;
}
// Remove the class ONLY when scrolling up and the element leaves the viewport
target.classList.remove('in-view');
}
});
},
{
threshold: 0.2 // Trigger when 20% or more of the element is visible
}
);
// Observe each animation object
animationObjects.forEach((el: HTMLElement): void => observer.observe(el));