Improved animation efficiency

This commit is contained in:
prototypa
2024-10-14 10:19:11 -04:00
parent a5520bbc85
commit 740b96c878

View File

@@ -164,10 +164,9 @@ import { UI } from 'astrowind:config';
<script is:inline> <script is:inline>
/* Inspired by: https://github.com/heidkaemper/tailwindcss-intersect */ /* Inspired by: https://github.com/heidkaemper/tailwindcss-intersect */
const Observer = { const Observer = {
observers: {}, observer: null,
visibleElementsQueue: [],
processing: false,
delayBetweenAnimations: 100, delayBetweenAnimations: 100,
animationCounter: 0,
start() { start() {
const selectors = [ const selectors = [
@@ -182,8 +181,6 @@ import { UI } from 'astrowind:config';
const elements = Array.from(document.querySelectorAll(selectors.join(','))); const elements = Array.from(document.querySelectorAll(selectors.join(',')));
elements.forEach((el) => el.setAttribute('no-intersect', ''));
const getThreshold = (element) => { const getThreshold = (element) => {
if (element.classList.contains('intersect-full')) return 0.99; if (element.classList.contains('intersect-full')) return 0.99;
if (element.classList.contains('intersect-half')) return 0.5; if (element.classList.contains('intersect-half')) return 0.5;
@@ -191,86 +188,63 @@ import { UI } from 'astrowind:config';
return 0; return 0;
}; };
Object.values(this.observers).forEach((observer) => observer.disconnect()); elements.forEach((el) => {
this.observers = {}; el.setAttribute('no-intersect', '');
el._intersectionThreshold = getThreshold(el);
});
const callback = (entries) => { const callback = (entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
requestAnimationFrame(() => {
const target = entry.target; const target = entry.target;
const intersectionRatio = entry.intersectionRatio;
const threshold = target._intersectionThreshold;
if (target.classList.contains('intersect-no-queue')) {
if (entry.isIntersecting) { if (entry.isIntersecting) {
if (target.classList.contains('intercept-no-queue')) {
target.removeAttribute('no-intersect'); target.removeAttribute('no-intersect');
if (target.classList.contains('intersect-once')) { if (target.classList.contains('intersect-once')) {
Object.values(this.observers).forEach((observer) => observer.unobserve(target)); this.observer.unobserve(target);
}
} else {
target.setAttribute('no-intersect', '');
} }
return; return;
} }
if (!this.visibleElementsQueue.includes(target)) { if (intersectionRatio >= threshold) {
this.visibleElementsQueue.push(target); if (!target.hasAttribute('data-animated')) {
} target.removeAttribute('no-intersect');
target.setAttribute('data-animated', 'true');
this.processQueue(); const delay = this.animationCounter * this.delayBetweenAnimations;
this.animationCounter++;
target.style.transitionDelay = `${delay}ms`;
target.style.animationDelay = `${delay}ms`;
if (target.classList.contains('intersect-once')) {
this.observer.unobserve(target);
}
}
} else { } else {
target.setAttribute('no-intersect', ''); target.setAttribute('no-intersect', '');
target.removeAttribute('data-animated');
target.style.transitionDelay = '';
target.style.animationDelay = '';
const index = this.visibleElementsQueue.indexOf(target); this.animationCounter = 0;
if (index > -1) {
this.visibleElementsQueue.splice(index, 1);
}
} }
}); });
});
}; };
this.observer = new IntersectionObserver(callback.bind(this), { threshold: [0, 0.25, 0.5, 0.99] });
elements.forEach((el) => { elements.forEach((el) => {
const threshold = getThreshold(el); this.observer.observe(el);
if (!this.observers[threshold]) {
this.observers[threshold] = new IntersectionObserver(callback, { threshold });
}
this.observers[threshold].observe(el);
}); });
}, },
async processQueue() {
if (this.processing) {
return;
}
this.processing = true;
while (this.visibleElementsQueue.length > 0) {
const element = this.visibleElementsQueue.shift();
element.removeAttribute('no-intersect');
if (element.classList.contains('intersect-once')) {
Object.values(this.observers).forEach((observer) => observer.unobserve(element));
}
if (this.isElementInViewport(element)) {
await this.delay(this.delayBetweenAnimations);
}
}
this.processing = false;
},
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
},
isElementInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
rect.right > 0
);
},
}; };
Observer.start(); Observer.start();