136 lines
3.7 KiB
Plaintext
136 lines
3.7 KiB
Plaintext
|
---
|
||
|
interface Props {
|
||
|
animation?: 'fade-up' | 'fade-down' | 'fade-left' | 'fade-right' | 'zoom-in' | 'zoom-out';
|
||
|
duration?: number; // in milliseconds
|
||
|
delay?: number; // in milliseconds
|
||
|
threshold?: number; // 0-1, percentage of element visible to trigger
|
||
|
rootMargin?: string; // CSS margin value
|
||
|
once?: boolean; // animate only once or every time element enters viewport
|
||
|
class?: string;
|
||
|
}
|
||
|
|
||
|
const {
|
||
|
animation = 'fade-up',
|
||
|
duration = 600,
|
||
|
delay = 0,
|
||
|
threshold = 0.1,
|
||
|
rootMargin = '0px',
|
||
|
once = true,
|
||
|
class: className = '',
|
||
|
} = Astro.props;
|
||
|
|
||
|
const id = `scroll-reveal-${Math.random().toString(36).substring(2, 9)}`;
|
||
|
---
|
||
|
|
||
|
<div
|
||
|
id={id}
|
||
|
class:list={['scroll-reveal', className]}
|
||
|
data-animation={animation}
|
||
|
data-duration={duration}
|
||
|
data-delay={delay}
|
||
|
data-threshold={threshold}
|
||
|
data-root-margin={rootMargin}
|
||
|
data-once={once}
|
||
|
>
|
||
|
<slot />
|
||
|
</div>
|
||
|
|
||
|
<style>
|
||
|
.scroll-reveal {
|
||
|
opacity: 0;
|
||
|
will-change: transform, opacity;
|
||
|
}
|
||
|
|
||
|
.scroll-reveal[data-animation="fade-up"] {
|
||
|
transform: translateY(30px);
|
||
|
}
|
||
|
|
||
|
.scroll-reveal[data-animation="fade-down"] {
|
||
|
transform: translateY(-30px);
|
||
|
}
|
||
|
|
||
|
.scroll-reveal[data-animation="fade-left"] {
|
||
|
transform: translateX(30px);
|
||
|
}
|
||
|
|
||
|
.scroll-reveal[data-animation="fade-right"] {
|
||
|
transform: translateX(-30px);
|
||
|
}
|
||
|
|
||
|
.scroll-reveal[data-animation="zoom-in"] {
|
||
|
transform: scale(0.9);
|
||
|
}
|
||
|
|
||
|
.scroll-reveal[data-animation="zoom-out"] {
|
||
|
transform: scale(1.1);
|
||
|
}
|
||
|
|
||
|
.scroll-reveal.revealed {
|
||
|
opacity: 1;
|
||
|
transform: translate(0) scale(1);
|
||
|
}
|
||
|
|
||
|
@media (prefers-reduced-motion: reduce) {
|
||
|
.scroll-reveal {
|
||
|
transition: none !important;
|
||
|
opacity: 1 !important;
|
||
|
transform: none !important;
|
||
|
}
|
||
|
}
|
||
|
</style>
|
||
|
|
||
|
<script>
|
||
|
// Initialize scroll reveal animations
|
||
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
const scrollRevealElements = document.querySelectorAll('.scroll-reveal');
|
||
|
|
||
|
if (!scrollRevealElements.length) return;
|
||
|
|
||
|
// Check if IntersectionObserver is supported
|
||
|
if ('IntersectionObserver' in window) {
|
||
|
scrollRevealElements.forEach(element => {
|
||
|
// Get animation parameters from data attributes
|
||
|
const duration = parseInt(element.getAttribute('data-duration') || '600', 10);
|
||
|
const delay = parseInt(element.getAttribute('data-delay') || '0', 10);
|
||
|
const threshold = parseFloat(element.getAttribute('data-threshold') || '0.1');
|
||
|
const rootMargin = element.getAttribute('data-root-margin') || '0px';
|
||
|
const once = element.getAttribute('data-once') === 'true';
|
||
|
|
||
|
// Set transition based on parameters
|
||
|
(element as HTMLElement).style.transition = `opacity ${duration}ms ease ${delay}ms, transform ${duration}ms ease ${delay}ms`;
|
||
|
|
||
|
// Create observer for this element
|
||
|
const observer = new IntersectionObserver(
|
||
|
(entries) => {
|
||
|
entries.forEach(entry => {
|
||
|
if (entry.isIntersecting) {
|
||
|
entry.target.classList.add('revealed');
|
||
|
|
||
|
// Unobserve if once is true
|
||
|
if (once) {
|
||
|
observer.unobserve(entry.target);
|
||
|
}
|
||
|
} else if (!once) {
|
||
|
// Remove class if element leaves viewport and once is false
|
||
|
entry.target.classList.remove('revealed');
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
{
|
||
|
threshold,
|
||
|
rootMargin
|
||
|
}
|
||
|
);
|
||
|
|
||
|
// Start observing
|
||
|
observer.observe(element);
|
||
|
});
|
||
|
} else {
|
||
|
// Fallback for browsers that don't support IntersectionObserver
|
||
|
scrollRevealElements.forEach(element => {
|
||
|
element.classList.add('revealed');
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
</script>
|