Performance Optimizations for Toolkit Page Card Animations
Summary
Implemented significant performance optimizations to the toolkit page card animations and hover effects, reducing DOM operations and leveraging hardware acceleration for smoother interactions.
Why Care
The toolkit page was experiencing performance issues with competing animations and hover effects, causing perceptible lag during user interaction. These optimizations dramatically improve the user experience by maintaining visual appeal while eliminating slowdowns, particularly important for users browsing through many tool cards.
Implementation
Changes Made
/Users/mpstaton/code/lossless-monorepo/site/src/components/basics/CardGrid.astro
- Optimized mouse movement handler to reduce unnecessary DOM operations
- Replaced individual card animations with container-based animation approach
- Improved CSS for card hover effects with hardware acceleration
- Reduced transition times for snappier feedback
- Added conditional application of expensive gradient calculations
/Users/mpstaton/code/lossless-monorepo/site/src/components/tool-components/TagCloud.astro
- Updated tag count display logic to be conditional on context
- Reduced padding and gap spacing for better visual density
/Users/mpstaton/code/lossless-monorepo/site/src/components/tool-components/ToolCard.astro
- Added 'card' class to ensure consistent targeting by animations
- Implemented enhanced hover animation with transform and box-shadow
/Users/mpstaton/code/lossless-monorepo/site/src/components/tool-components/BareToolCard.astro
- Added 'card' class to ensure consistent targeting by animations
Technical Details
Mouse Movement Handler Optimization
javascript
// Before: Inefficient handler updating all cards on every mouse move
const handleMouseMove = (e) => {
const cards = document.getElementsByClassName("card");
for (const card of cards) {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
}
};
// After: Optimized handler with requestAnimationFrame and selective processing
const handleMouseMove = (e) => {
if (window.mouseMoveRAF) return;
window.mouseMoveRAF = requestAnimationFrame(() => {
const cards = document.querySelectorAll('.card:hover');
if (cards.length === 0) {
window.mouseMoveRAF = null;
return;
}
const card = cards[0];
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
window.mouseMoveRAF = null;
});
};
Animation Approach Improvement
javascript
// Before: Individual card animations with multiple timeouts
toolCards.forEach(card => {
card.classList.add('card-hidden');
});
toolCards.forEach((card, index) => {
setTimeout(() => {
card.classList.add('card-appear');
}, 50 * index); // 50ms delay between each card
});
// After: Container-based animation with single timeout
document.querySelector('.cards-container')?.classList.add('animate-cards');
setTimeout(() => {
document.querySelector('.cards-container')?.classList.add('animation-complete');
}, 1000); // Fixed time for all cards
CSS Performance Enhancements
css
/* Before: Always applying expensive gradients */
:global(.card)::before {
background: radial-gradient(
800px circle at var(--mouse-x) var(--mouse-y),
var(--clr-lossless-primary-glass),
transparent 40%
);
z-index: -1;
}
/* After: Conditionally applying gradients only on hover */
:global(.card:hover)::before {
background: radial-gradient(
800px circle at var(--mouse-x) var(--mouse-y),
var(--clr-lossless-primary-glass),
transparent 40%
);
z-index: -1;
opacity: 1;
}
Hardware Acceleration
css
:global(.card)::before,
:global(.card)::after {
/* Use hardware acceleration */
transform: translateZ(0);
will-change: transform;
/* Reduce transition time for better performance */
transition: opacity 300ms;
}
Integration Points
- These changes affect the visual behavior of the toolkit page without altering its core functionality
- The optimizations maintain visual consistency with the existing design system
- The changes respect user preferences for reduced motion when applicable
- The performance improvements are most noticeable on pages with many tool cards
Documentation
- These optimizations follow the principles outlined in the CSS Animation System specification
- The changes align with the hover effect patterns documented in the component library
- Performance best practices implemented include:
- Using requestAnimationFrame for DOM updates
- Leveraging hardware acceleration with transform: translateZ(0)
- Limiting expensive calculations to only when needed
- Using passive event listeners for better scrolling performance
- Batching DOM reads and writes to prevent layout thrashing