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