Static pages feel flat. Scroll animations add life โ elements fade in, slide up, and reveal as users scroll. Here's how to add them to your Tailwind CSS site using Motion.js.
Why Motion.js?
Motion.js (formerly Framer Motion's standalone engine) is lightweight, performant, and works with any framework. It provides animate() for animations and inView() for scroll-triggered effects.
<script src="https://cdn.jsdelivr.net/npm/motion@latest/dist/motion.js"></script>
Pattern 1: Fade In on Scroll
The most common animation. Elements start invisible and fade in when they enter the viewport.
const { animate, inView } = window.Motion;
// Select elements to animate
const cards = document.querySelectorAll('[data-motion="card"]');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(30px)';
inView(card, () => {
animate(card, {
opacity: [0, 1],
transform: ['translateY(30px)', 'translateY(0)']
}, {
duration: 0.7,
delay: index * 0.15,
easing: 'ease-out'
});
}, { amount: 0.3 });
});
Pattern 2: Staggered Cards
Cards animate in sequence with a delay between each. Creates a cascading effect that draws the eye.
Pattern 3: Word-by-Word Text Reveal
Split headings into individual words and animate each one. Creates a premium, editorial feel.
function splitWords(element) {
const text = element.textContent;
const words = text.split(/(s+)/);
element.innerHTML = '';
words.forEach(word => {
if (word.trim()) {
const span = document.createElement('span');
span.style.display = 'inline-block';
span.style.opacity = '0';
span.textContent = word;
element.appendChild(span);
} else {
element.appendChild(document.createTextNode(word));
}
});
element.style.opacity = '1';
}
// Usage
const heading = document.querySelector('[data-animate="heading"]');
splitWords(heading);
inView(heading, () => {
const words = heading.querySelectorAll('span');
words.forEach((word, i) => {
animate(word, {
opacity: [0, 1],
transform: ['translateY(20px)', 'translateY(0)'],
filter: ['blur(10px)', 'blur(0px)']
}, { duration: 0.6, delay: i * 0.08, easing: 'ease-out' });
});
}, { amount: 0.3 });
Pattern 4: Scale Up Images
Images start slightly smaller and scale to full size. Subtle but effective.
const images = document.querySelectorAll('[data-motion="image"]');
images.forEach(img => {
img.style.opacity = '0';
img.style.transform = 'scale(0.95)';
inView(img, () => {
animate(img, {
opacity: [0, 1],
transform: ['scale(0.95)', 'scale(1)']
}, { duration: 0.8, easing: 'ease-out' });
}, { amount: 0.3 });
});
๐ก Pro tip: Every block on OpenTailwind includes scroll animations out of the box. Browse any category to see these patterns in action.
Performance Tips
- Only animate
opacity,transform, andfilterโ these are GPU-accelerated - Use
inView()instead of IntersectionObserver directly โ Motion.js handles cleanup - Set initial hidden state via CSS (
[data-motion] { opacity: 0; }) to prevent flash - Keep durations under 1 second โ animations should enhance, not delay
