CSS Repaints & Reflows đŸ”Ĩ

Understand browser rendering performance and optimize your CSS for buttery-smooth experiences.

What are Repaints & Reflows?

🔄 Reflow (Layout)

When the browser recalculates the position and geometry of elements.

  • Changing width/height/margin/padding
  • Adding/removing DOM elements
  • Changing font size or family
  • Reading layout properties (offsetWidth, etc.)
đŸ”Ĩ Expensive: Can cause layout thrashing

🎨 Repaint (Paint)

When the browser redraws pixels without changing layout.

  • Changing color/background-color
  • Changing visibility/opacity
  • Adding box-shadows or borders
  • Transforms that don't affect layout
⚡ Moderate: Less expensive than reflow

📊 Live Performance Dashboard

Interactive Features:

  • Real-time FPS monitoring - Watch performance impact
  • Reflow & repaint counters - See exactly what's happening
  • Visual indicators - Know when expensive operations occur
  • Compare strategies - See optimized vs unoptimized code

Performance Dashboard Implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>📊 Live Dashboard - Repaints & Reflows Demo</title>
  <style>
    .dashboard {
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      min-height: 100vh;
      padding: 2rem;
      color: white;
      font-family: 'Inter', system-ui, sans-serif;
    }
    
    .performance-monitor {
      background: rgba(255,255,255,0.1);
      backdrop-filter: blur(10px);
      padding: 2rem;
      border-radius: 20px;
      margin-bottom: 2rem;
      border: 1px solid rgba(255,255,255,0.2);
    }
    
    .metrics-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 1rem;
      margin: 2rem 0;
    }
    
    .metric-card {
      background: rgba(255,255,255,0.15);
      padding: 1.5rem;
      border-radius: 15px;
      text-align: center;
      transition: all 0.3s ease;
      border: 1px solid rgba(255,255,255,0.1);
    }
    
    .metric-value {
      font-size: 2.5rem;
      font-weight: bold;
      margin-bottom: 0.5rem;
    }
    
    .controls {
      display: flex;
      gap: 1rem;
      margin: 2rem 0;
      flex-wrap: wrap;
    }
    
    .btn {
      padding: 1rem 2rem;
      border: none;
      border-radius: 10px;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s ease;
    }
    
    .btn-danger {
      background: #e74c3c;
      color: white;
    }
    
    .btn-success {
      background: #2ecc71;
      color: white;
    }
    
    .btn-warning {
      background: #f39c12;
      color: white;
    }
    
    .data-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 1rem;
      margin-top: 2rem;
    }
    
    .data-card {
      background: rgba(255,255,255,0.1);
      padding: 1.5rem;
      border-radius: 15px;
      min-height: 200px;
    }
    
    .reflow-indicator {
      position: fixed;
      top: 20px;
      right: 20px;
      background: #e74c3c;
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 20px;
      font-size: 0.8rem;
      font-weight: bold;
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    
    .repaint-indicator {
      position: fixed;
      top: 60px;
      right: 20px;
      background: #f39c12;
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 20px;
      font-size: 0.8rem;
      font-weight: bold;
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    
    .expensive-operation {
      background: rgba(231, 76, 60, 0.3);
      border: 2px solid #e74c3c;
      animation: pulse 0.5s ease;
    }
    
    .cheap-operation {
      background: rgba(46, 204, 113, 0.3);
      border: 2px solid #2ecc71;
    }
    
    @keyframes pulse {
      0% { transform: scale(1); }
      50% { transform: scale(1.02); }
      100% { transform: scale(1); }
    }
    
    .particles {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
      z-index: -1;
    }
    
    .particle {
      position: absolute;
      background: rgba(255,255,255,0.1);
      border-radius: 50%;
    }
  </style>
</head>
<body>
  <div class="dashboard">
    <!-- đŸŽ¯ Performance Indicators -->
    <div class="reflow-indicator" id="reflowIndicator">🔄 REFLOW</div>
    <div class="repaint-indicator" id="repaintIndicator">🎨 REPAINT</div>
    
    <div class="particles" id="particles"></div>
    
    <header>
      <h1>🚀 Live Performance Dashboard</h1>
      <p>Monitor repaints and reflows in real-time</p>
    </header>
    
    <section class="performance-monitor">
      <h2>📊 Performance Metrics</h2>
      
      <div class="metrics-grid">
        <div class="metric-card" id="fpsMetric">
          <div class="metric-value">60</div>
          <div class="metric-label">FPS</div>
        </div>
        <div class="metric-card" id="reflowMetric">
          <div class="metric-value">0</div>
          <div class="metric-label">Reflows</div>
        </div>
        <div class="metric-card" id="repaintMetric">
          <div class="metric-value">0</div>
          <div class="metric-label">Repaints</div>
        </div>
        <div class="metric-card" id="memoryMetric">
          <div class="metric-value">0 MB</div>
          <div class="metric-label">Memory</div>
        </div>
      </div>
      
      <div class="controls">
        <button class="btn btn-danger" onclick="triggerReflowHell()">
          đŸ”Ĩ Trigger Reflow Hell
        </button>
        <button class="btn btn-warning" onclick="triggerRepaintStorm()">
          ⚡ Trigger Repaint Storm
        </button>
        <button class="btn btn-success" onclick="optimizedAnimation()">
          🚀 Optimized Animation
        </button>
        <button class="btn" onclick="resetMetrics()">
          🔄 Reset Metrics
        </button>
      </div>
    </section>
    
    <section class="data-grid">
      <div class="data-card" id="card1">
        <h3>User Activity</h3>
        <div class="activity-chart" style="height: 120px; background: rgba(255,255,255,0.1); border-radius: 10px; margin-top: 1rem;"></div>
      </div>
      <div class="data-card" id="card2">
        <h3>System Load</h3>
        <div class="load-chart" style="height: 120px; background: rgba(255,255,255,0.1); border-radius: 10px; margin-top: 1rem;"></div>
      </div>
      <div class="data-card" id="card3">
        <h3>Network Traffic</h3>
        <div class="network-chart" style="height: 120px; background: rgba(255,255,255,0.1); border-radius: 10px; margin-top: 1rem;"></div>
      </div>
    </section>
  </div>

  <script>
    // đŸŽ¯ Performance Monitoring
    let reflowCount = 0;
    let repaintCount = 0;
    let lastFrameTime = performance.now();
    let frameCount = 0;
    let currentFPS = 60;
    
    const reflowIndicator = document.getElementById('reflowIndicator');
    const repaintIndicator = document.getElementById('repaintIndicator');
    const reflowMetric = document.getElementById('reflowMetric');
    const repaintMetric = document.getElementById('repaintMetric');
    const fpsMetric = document.getElementById('fpsMetric');
    const memoryMetric = document.getElementById('memoryMetric');
    
    function showReflow() {
      reflowCount++;
      reflowMetric.querySelector('.metric-value').textContent = reflowCount;
      reflowIndicator.style.opacity = '1';
      setTimeout(() => reflowIndicator.style.opacity = '0', 500);
    }
    
    function showRepaint() {
      repaintCount++;
      repaintMetric.querySelector('.metric-value').textContent = repaintCount;
      repaintIndicator.style.opacity = '1';
      setTimeout(() => repaintIndicator.style.opacity = '0', 500);
    }
    
    function updateFPS() {
      frameCount++;
      const currentTime = performance.now();
      
      if (currentTime >= lastFrameTime + 1000) {
        currentFPS = Math.round((frameCount * 1000) / (currentTime - lastFrameTime));
        fpsMetric.querySelector('.metric-value').textContent = currentFPS;
        frameCount = 0;
        lastFrameTime = currentTime;
      }
      
      // Update memory usage
      if (performance.memory) {
        const memoryMB = Math.round(performance.memory.usedJSHeapSize / 1048576);
        memoryMetric.querySelector('.metric-value').textContent = memoryMB + ' MB';
      }
      
      requestAnimationFrame(updateFPS);
    }
    
    // 🚨 EXPENSIVE OPERATIONS
    function triggerReflowHell() {
      const cards = document.querySelectorAll('.data-card');
      const metrics = document.querySelectorAll('.metric-card');
      
      // đŸ”Ĩ TRIGGER MULTIPLE REFLOWS (Very Expensive)
      cards.forEach((card, index) => {
        // Multiple synchronous layout-triggering operations
        card.style.width = (300 + Math.random() * 50) + 'px'; // đŸ”Ĩ Reflow
        showReflow();
        
        card.style.height = (200 + Math.random() * 30) + 'px'; // đŸ”Ĩ Reflow
        showReflow();
        
        card.style.margin = (10 + Math.random() * 5) + 'px'; // đŸ”Ĩ Reflow
        showReflow();
        
        card.style.padding = (20 + Math.random() * 10) + 'px'; // đŸ”Ĩ Reflow
        showReflow();
        
        card.classList.add('expensive-operation');
        setTimeout(() => card.classList.remove('expensive-operation'), 1000);
      });
      
      // Force layout thrashing
      setTimeout(() => {
        metrics.forEach(metric => {
          metric.style.width = metric.offsetWidth + 10 + 'px'; // đŸ”Ĩ Reflow
          showReflow();
        });
      }, 100);
    }
    
    function triggerRepaintStorm() {
      const cards = document.querySelectorAll('.data-card');
      
      // ⚡ TRIGGER MULTIPLE REPAINTS (Moderately Expensive)
      cards.forEach((card, index) => {
        // Multiple paint-triggering operations
        card.style.background = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.3)`; // 🎨 Repaint
        showRepaint();
        
        card.style.color = `hsl(${Math.random() * 360}, 100%, 70%)`; // 🎨 Repaint
        showRepaint();
        
        card.style.border = `2px solid hsl(${Math.random() * 360}, 100%, 60%)`; // 🎨 Repaint
        showRepaint();
        
        card.style.boxShadow = `0 0 20px rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.5)`; // 🎨 Repaint
        showRepaint();
      });
    }
    
    // 🚀 OPTIMIZED OPERATIONS
    function optimizedAnimation() {
      const cards = document.querySelectorAll('.data-card');
      
      // ✅ USE TRANSFORM (No Reflow, Cheap Repaint)
      cards.forEach((card, index) => {
        card.style.transform = 'translateY(0) scale(1)';
        card.style.transition = 'transform 0.5s ease';
        
        setTimeout(() => {
          card.style.transform = 'translateY(-10px) scale(1.02)'; // ✅ No reflow!
          card.classList.add('cheap-operation');
        }, index * 100);
      });
      
      // ✅ BATCH DOM OPERATIONS
      requestAnimationFrame(() => {
        const fragment = document.createDocumentFragment();
        
        // Create particles efficiently
        for (let i = 0; i < 20; i++) {
          const particle = document.createElement('div');
          particle.className = 'particle';
          particle.style.cssText = `
            left: ${Math.random() * 100}vw;
            top: ${Math.random() * 100}vh;
            width: ${Math.random() * 10 + 5}px;
            height: ${Math.random() * 10 + 5}px;
            background: hsl(${Math.random() * 360}, 100%, 70%);
            animation: float ${Math.random() * 3 + 2}s ease-in-out infinite;
          `;
          fragment.appendChild(particle);
        }
        
        document.getElementById('particles').appendChild(fragment);
      });
    }
    
    function resetMetrics() {
      reflowCount = 0;
      repaintCount = 0;
      reflowMetric.querySelector('.metric-value').textContent = '0';
      repaintMetric.querySelector('.metric-value').textContent = '0';
      
      // Reset styles
      const cards = document.querySelectorAll('.data-card');
      cards.forEach(card => {
        card.style.cssText = '';
        card.className = 'data-card';
      });
      
      document.getElementById('particles').innerHTML = '';
    }
    
    // Initialize particles and FPS counter
    function createParticles() {
      const particlesContainer = document.getElementById('particles');
      for (let i = 0; i < 15; i++) {
        const particle = document.createElement('div');
        particle.className = 'particle';
        particle.style.cssText = `
          left: ${Math.random() * 100}vw;
          top: ${Math.random() * 100}vh;
          width: ${Math.random() * 8 + 2}px;
          height: ${Math.random() * 8 + 2}px;
          background: rgba(255,255,255,${Math.random() * 0.3});
          animation: float ${Math.random() * 6 + 3}s ease-in-out infinite;
        `;
        particlesContainer.appendChild(particle);
      }
      
      // Add float animation
      const style = document.createElement('style');
      style.textContent = `
        @keyframes float {
          0%, 100% { transform: translateY(0) rotate(0deg); }
          50% { transform: translateY(-20px) rotate(180deg); }
        }
      `;
      document.head.appendChild(style);
    }
    
    // Start monitoring
    createParticles();
    updateFPS();
  </script>
</body>
</html>

đŸ–ŧī¸ Smart Image Gallery

❌ What to Avoid

đŸ”ĨMultiple synchronous layout changes
📏Reading layout properties after writes
🔄Forced synchronous layouts

✅ Best Practices

🚀Use transform and opacity
đŸ“ĻBatch DOM operations
đŸŽ¯Use requestAnimationFrame

Gallery Implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>đŸ–ŧī¸ Smart Gallery - Repaint & Reflow Optimization</title>
  <style>
    .gallery {
      max-width: 1200px;
      margin: 0 auto;
      padding: 2rem;
      font-family: 'Inter', system-ui, sans-serif;
    }
    
    .gallery-header {
      text-align: center;
      margin-bottom: 3rem;
    }
    
    .performance-badge {
      display: inline-block;
      background: #2ecc71;
      color: white;
      padding: 0.5rem 1rem;
      border-radius: 20px;
      font-size: 0.9rem;
      font-weight: 600;
      margin: 0.5rem;
    }
    
    .performance-badge.slow {
      background: #e74c3c;
    }
    
    .controls {
      display: flex;
      gap: 1rem;
      justify-content: center;
      margin: 2rem 0;
      flex-wrap: wrap;
    }
    
    .btn {
      padding: 1rem 2rem;
      border: none;
      border-radius: 10px;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s ease;
    }
    
    .btn-slow {
      background: #e74c3c;
      color: white;
    }
    
    .btn-fast {
      background: #2ecc71;
      color: white;
    }
    
    .gallery-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 1.5rem;
      margin: 2rem 0;
    }
    
    .gallery-item {
      background: #f8f9fa;
      border-radius: 15px;
      overflow: hidden;
      transition: all 0.3s ease;
      border: 3px solid transparent;
    }
    
    .gallery-item.reflowing {
      border-color: #e74c3c;
      animation: reflowPulse 0.5s ease;
    }
    
    .gallery-item.repainting {
      border-color: #f39c12;
      animation: repaintPulse 0.5s ease;
    }
    
    .gallery-item.optimized {
      border-color: #2ecc71;
    }
    
    .item-image {
      width: 100%;
      height: 200px;
      background: linear-gradient(45deg, #667eea, #764ba2);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 3rem;
      color: white;
    }
    
    .item-content {
      padding: 1.5rem;
    }
    
    .item-title {
      font-size: 1.2rem;
      font-weight: 600;
      margin-bottom: 0.5rem;
    }
    
    .item-description {
      color: #666;
      line-height: 1.5;
    }
    
    .stats-panel {
      background: #34495e;
      color: white;
      padding: 1.5rem;
      border-radius: 15px;
      margin: 2rem 0;
    }
    
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      gap: 1rem;
      text-align: center;
    }
    
    .stat-value {
      font-size: 2rem;
      font-weight: bold;
      margin-bottom: 0.5rem;
    }
    
    .stat-label {
      font-size: 0.9rem;
      opacity: 0.8;
    }
    
    @keyframes reflowPulse {
      0% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.7); }
      70% { box-shadow: 0 0 0 10px rgba(231, 76, 60, 0); }
      100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0); }
    }
    
    @keyframes repaintPulse {
      0% { box-shadow: 0 0 0 0 rgba(243, 156, 18, 0.7); }
      70% { box-shadow: 0 0 0 10px rgba(243, 156, 18, 0); }
      100% { box-shadow: 0 0 0 0 rgba(243, 156, 18, 0); }
    }
    
    .comparison {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 2rem;
      margin: 3rem 0;
    }
    
    .comparison-card {
      background: white;
      padding: 2rem;
      border-radius: 15px;
      box-shadow: 0 5px 15px rgba(0,0,0,0.1);
    }
    
    .code-example {
      background: #2c3e50;
      color: #ecf0f1;
      padding: 1rem;
      border-radius: 8px;
      font-family: 'Fira Code', monospace;
      font-size: 0.9rem;
      margin: 1rem 0;
    }
    
    .bad {
      border-left: 4px solid #e74c3c;
    }
    
    .good {
      border-left: 4px solid #2ecc71;
    }
  </style>
</head>
<body>
  <div class="gallery">
    <header class="gallery-header">
      <h1>đŸ–ŧī¸ Smart Image Gallery</h1>
      <p>See the difference between expensive and optimized operations</p>
      <div>
        <span class="performance-badge slow">đŸ”Ĩ Reflow Triggered</span>
        <span class="performance-badge">🎨 Repaint Triggered</span>
        <span class="performance-badge">🚀 Optimized</span>
      </div>
    </header>
    
    <div class="controls">
      <button class="btn btn-slow" onclick="triggerExpensiveOperations()">
        đŸ”Ĩ Trigger Expensive Operations
      </button>
      <button class="btn btn-fast" onclick="triggerOptimizedOperations()">
        🚀 Trigger Optimized Operations
      </button>
      <button class="btn" onclick="resetGallery()">
        🔄 Reset Gallery
      </button>
    </div>
    
    <div class="stats-panel">
      <div class="stats-grid">
        <div class="stat">
          <div class="stat-value" id="reflowCount">0</div>
          <div class="stat-label">Reflows</div>
        </div>
        <div class="stat">
          <div class="stat-value" id="repaintCount">0</div>
          <div class="stat-label">Repaints</div>
        </div>
        <div class="stat">
          <div class="stat-value" id="operationTime">0ms</div>
          <div class="stat-label">Operation Time</div>
        </div>
        <div class="stat">
          <div class="stat-value" id="fps">60</div>
          <div class="stat-label">FPS</div>
        </div>
      </div>
    </div>
    
    <div class="gallery-grid" id="galleryGrid">
      <!-- Gallery items will be generated by JavaScript -->
    </div>
    
    <div class="comparison">
      <div class="comparison-card">
        <h3>❌ Expensive Operations</h3>
        <p>These trigger reflows and hurt performance:</p>
        
        <div class="code-example bad">
          // đŸ”Ĩ Multiple reflows<br>
          element.style.width = '200px';<br>
          element.style.height = '150px';<br>
          element.style.margin = '10px';<br>
          element.style.padding = '20px';
        </div>
        
        <div class="code-example bad">
          // đŸ”Ĩ Reading layout properties<br>
          const width = element.offsetWidth;<br>
          element.style.width = width + 10 + 'px';
        </div>
      </div>
      
      <div class="comparison-card">
        <h3>✅ Optimized Operations</h3>
        <p>These are cheap and performant:</p>
        
        <div class="code-example good">
          // 🚀 No reflow<br>
          element.style.transform = 'scale(1.1)';<br>
          element.style.opacity = '0.8';
        </div>
        
        <div class="code-example good">
          // 🚀 Batch DOM operations<br>
          const fragment = document.createDocumentFragment();<br>
          // Add elements to fragment<br>
          container.appendChild(fragment);
        </div>
      </div>
    </div>
  </div>

  <script>
    let reflowCount = 0;
    let repaintCount = 0;
    let lastFrameTime = performance.now();
    let frameCount = 0;
    
    // Generate gallery items
    function generateGalleryItems() {
      const grid = document.getElementById('galleryGrid');
      const items = [];
      
      for (let i = 1; i <= 12; i++) {
        items.push(`
          <div class="gallery-item" id="item-${i}">
            <div class="item-image">
              ${getRandomEmoji()}
            </div>
            <div class="item-content">
              <div class="item-title">Artwork #${i}</div>
              <div class="item-description">
                Beautiful digital creation with amazing colors and composition.
              </div>
            </div>
          </div>
        `);
      }
      
      grid.innerHTML = items.join('');
    }
    
    function getRandomEmoji() {
      const emojis = ['🎨', 'đŸ–ŧī¸', '🌟', '✨', 'đŸ”Ĩ', 'đŸ’Ģ', 'đŸŽĒ', 'đŸĻ‹', 'đŸŒē', '🌈'];
      return emojis[Math.floor(Math.random() * emojis.length)];
    }
    
    function updateStats() {
      document.getElementById('reflowCount').textContent = reflowCount;
      document.getElementById('repaintCount').textContent = repaintCount;
      
      frameCount++;
      const currentTime = performance.now();
      if (currentTime >= lastFrameTime + 1000) {
        const fps = Math.round((frameCount * 1000) / (currentTime - lastFrameTime));
        document.getElementById('fps').textContent = fps;
        frameCount = 0;
        lastFrameTime = currentTime;
      }
      
      requestAnimationFrame(updateStats);
    }
    
    // 🚨 EXPENSIVE OPERATIONS
    function triggerExpensiveOperations() {
      const startTime = performance.now();
      const items = document.querySelectorAll('.gallery-item');
      
      items.forEach((item, index) => {
        // đŸ”Ĩ TRIGGER MULTIPLE REFLOWS
        setTimeout(() => {
          item.classList.add('reflowing');
          
          // Multiple layout-triggering changes
          item.style.width = (250 + Math.random() * 50) + 'px'; // Reflow
          reflowCount++;
          
          item.style.height = (300 + Math.random() * 40) + 'px'; // Reflow
          reflowCount++;
          
          item.style.margin = (10 + Math.random() * 5) + 'px'; // Reflow
          reflowCount++;
          
          item.offsetHeight; // Force reflow
          reflowCount++;
          
          // 🎨 TRIGGER REPAINTS
          item.style.background = `hsl(${Math.random() * 360}, 70%, 95%)`; // Repaint
          repaintCount++;
          
          item.style.borderRadius = (15 + Math.random() * 10) + 'px'; // Repaint
          repaintCount++;
          
          setTimeout(() => item.classList.remove('reflowing'), 1000);
        }, index * 50);
      });
      
      const endTime = performance.now();
      document.getElementById('operationTime').textContent = Math.round(endTime - startTime) + 'ms';
    }
    
    // 🚀 OPTIMIZED OPERATIONS
    function triggerOptimizedOperations() {
      const startTime = performance.now();
      const items = document.querySelectorAll('.gallery-item');
      
      // ✅ USE TRANSFORM (No reflow)
      items.forEach((item, index) => {
        setTimeout(() => {
          item.classList.add('optimized');
          item.style.transform = 'translateY(-10px) scale(1.05)'; // No reflow!
          item.style.transition = 'all 0.3s ease';
          item.style.opacity = '0.9'; // Cheap repaint
          repaintCount++;
        }, index * 30);
      });
      
      // ✅ BATCH DOM READ/WRITE
      requestAnimationFrame(() => {
        // Batch all reads
        const measurements = Array.from(items).map(item => ({
          element: item,
          width: item.offsetWidth
        }));
        
        // Batch all writes
        requestAnimationFrame(() => {
          measurements.forEach(({ element, width }) => {
            element.style.width = (width + 20) + 'px'; // Single reflow
            reflowCount++;
          });
        });
      });
      
      const endTime = performance.now();
      document.getElementById('operationTime').textContent = Math.round(endTime - startTime) + 'ms';
    }
    
    function resetGallery() {
      const items = document.querySelectorAll('.gallery-item');
      
      items.forEach(item => {
        item.style.cssText = '';
        item.className = 'gallery-item';
      });
      
      reflowCount = 0;
      repaintCount = 0;
      document.getElementById('operationTime').textContent = '0ms';
    }
    
    // Initialize
    generateGalleryItems();
    updateStats();
  </script>
</body>
</html>

đŸŽŦ Animation Performance Studio

Side-by-Side Comparison

See the dramatic difference between expensive animations (using top/left) and optimized animations (using transform) running simultaneously.

// Left: top/left = Reflows every frame
// Right: transform = GPU accelerated

Animation Studio Implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>đŸŽŦ Animation Studio - Performance Comparison</title>
  <style>
    .studio {
      background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
      min-height: 100vh;
      padding: 2rem;
      color: white;
      font-family: 'Inter', system-ui, sans-serif;
    }
    
    .studio-header {
      text-align: center;
      margin-bottom: 3rem;
    }
    
    .animation-stage {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 2rem;
      margin: 2rem 0;
    }
    
    .stage {
      background: rgba(255,255,255,0.1);
      backdrop-filter: blur(10px);
      border-radius: 20px;
      padding: 2rem;
      min-height: 400px;
      position: relative;
      overflow: hidden;
    }
    
    .stage-title {
      text-align: center;
      margin-bottom: 2rem;
      font-size: 1.5rem;
      font-weight: 600;
    }
    
    .animated-element {
      width: 80px;
      height: 80px;
      border-radius: 15px;
      position: absolute;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.5rem;
      font-weight: bold;
    }
    
    .expensive-element {
      background: linear-gradient(45deg, #e74c3c, #c0392b);
      left: 50%;
      transform: translateX(-50%);
    }
    
    .optimized-element {
      background: linear-gradient(45deg, #2ecc71, #27ae60);
      left: 50%;
      transform: translateX(-50%);
    }
    
    .controls {
      display: flex;
      gap: 1rem;
      justify-content: center;
      margin: 2rem 0;
      flex-wrap: wrap;
    }
    
    .btn {
      padding: 1rem 2rem;
      border: none;
      border-radius: 10px;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s ease;
    }
    
    .btn-primary {
      background: #3498db;
      color: white;
    }
    
    .performance-metrics {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 2rem;
      margin: 2rem 0;
    }
    
    .metric-panel {
      background: rgba(255,255,255,0.1);
      padding: 1.5rem;
      border-radius: 15px;
      text-align: center;
    }
    
    .metric-value {
      font-size: 2rem;
      font-weight: bold;
      margin-bottom: 0.5rem;
    }
    
    .metric-label {
      font-size: 0.9rem;
      opacity: 0.8;
    }
    
    .warning {
      color: #e74c3c;
      font-weight: bold;
    }
    
    .success {
      color: #2ecc71;
      font-weight: bold;
    }
    
    .code-comparison {
      background: rgba(0,0,0,0.3);
      padding: 2rem;
      border-radius: 15px;
      margin: 2rem 0;
    }
    
    .code-block {
      background: #2c3e50;
      color: #ecf0f1;
      padding: 1rem;
      border-radius: 8px;
      font-family: 'Fira Code', monospace;
      font-size: 0.9rem;
      margin: 1rem 0;
    }
    
    .bad-code {
      border-left: 4px solid #e74c3c;
    }
    
    .good-code {
      border-left: 4px solid #2ecc71;
    }
    
    @keyframes expensiveMove {
      0% { top: 50px; left: 50px; }
      25% { top: 50px; left: 250px; }
      50% { top: 250px; left: 250px; }
      75% { top: 250px; left: 50px; }
      100% { top: 50px; left: 50px; }
    }
    
    @keyframes optimizedMove {
      0% { transform: translate(50px, 50px); }
      25% { transform: translate(250px, 50px); }
      50% { transform: translate(250px, 250px); }
      75% { transform: translate(50px, 250px); }
      100% { transform: translate(50px, 50px); }
    }
  </style>
</head>
<body>
  <div class="studio">
    <header class="studio-header">
      <h1>đŸŽŦ Animation Performance Studio</h1>
      <p>Compare expensive vs optimized animations in real-time</p>
    </header>
    
    <div class="performance-metrics">
      <div class="metric-panel">
        <div class="metric-value warning" id="expensiveFPS">60</div>
        <div class="metric-label">Expensive Animation FPS</div>
      </div>
      <div class="metric-panel">
        <div class="metric-value success" id="optimizedFPS">60</div>
        <div class="metric-label">Optimized Animation FPS</div>
      </div>
    </div>
    
    <div class="controls">
      <button class="btn btn-primary" onclick="startAnimations()">
        đŸŽŦ Start Animations
      </button>
      <button class="btn" onclick="stopAnimations()">
        âšī¸ Stop Animations
      </button>
      <button class="btn" onclick="resetAnimations()">
        🔄 Reset
      </button>
    </div>
    
    <div class="animation-stage">
      <div class="stage">
        <div class="stage-title">❌ Expensive Animation</div>
        <div class="animated-element expensive-element" id="expensiveBox">
          đŸ”Ĩ
        </div>
        <div class="performance-info" style="position: absolute; bottom: 1rem; left: 1rem;">
          <div>Triggers: <strong>Reflows</strong></div>
          <div>Performance: <strong class="warning">Poor</strong></div>
        </div>
      </div>
      
      <div class="stage">
        <div class="stage-title">✅ Optimized Animation</div>
        <div class="animated-element optimized-element" id="optimizedBox">
          🚀
        </div>
        <div class="performance-info" style="position: absolute; bottom: 1rem; left: 1rem;">
          <div>Triggers: <strong>Compositing</strong></div>
          <div>Performance: <strong class="success">Excellent</strong></div>
        </div>
      </div>
    </div>
    
    <div class="code-comparison">
      <h3>🔄 What's Happening Behind the Scenes</h3>
      
      <div class="code-block bad-code">
        // đŸ”Ĩ EXPENSIVE: Triggers reflows<br>
        function animateExpensive() {<br>
        &nbsp;&nbsp;const box = document.getElementById('expensiveBox');<br>
        &nbsp;&nbsp;box.style.left = newLeft + 'px';  // Reflow!<br>
        &nbsp;&nbsp;box.style.top = newTop + 'px';    // Reflow!<br>
        &nbsp;&nbsp;box.offsetHeight; // Force reflow<br>
        }
      </div>
      
      <div class="code-block good-code">
        // 🚀 OPTIMIZED: Uses compositor<br>
        function animateOptimized() {<br>
        &nbsp;&nbsp;const box = document.getElementById('optimizedBox');<br>
        &nbsp;&nbsp;box.style.transform = `translate(${newX}px, ${newY}px)`;<br>
        &nbsp;&nbsp;// No reflow! GPU accelerated<br>
        }
      </div>
    </div>
  </div>

  <script>
    let animationRunning = false;
    let expensiveFrameCount = 0;
    let optimizedFrameCount = 0;
    let lastExpensiveTime = performance.now();
    let lastOptimizedTime = performance.now();
    let expensiveFPS = 60;
    let optimizedFPS = 60;
    
    const expensiveBox = document.getElementById('expensiveBox');
    const optimizedBox = document.getElementById('optimizedBox');
    
    function startAnimations() {
      if (animationRunning) return;
      animationRunning = true;
      
      // 🚨 Expensive animation using top/left
      function animateExpensive() {
        if (!animationRunning) return;
        
        const time = performance.now();
        const progress = (time % 4000) / 4000;
        
        // đŸ”Ĩ EXPENSIVE: Using top/left (triggers reflow)
        const angle = progress * Math.PI * 2;
        const radius = 100;
        
        const x = 150 + Math.cos(angle) * radius;
        const y = 150 + Math.sin(angle) * radius;
        
        expensiveBox.style.left = x + 'px'; // Reflow!
        expensiveBox.style.top = y + 'px';  // Reflow!
        
        expensiveFrameCount++;
        const currentTime = performance.now();
        if (currentTime >= lastExpensiveTime + 1000) {
          expensiveFPS = Math.round((expensiveFrameCount * 1000) / (currentTime - lastExpensiveTime));
          document.getElementById('expensiveFPS').textContent = expensiveFPS;
          expensiveFrameCount = 0;
          lastExpensiveTime = currentTime;
        }
        
        requestAnimationFrame(animateExpensive);
      }
      
      // 🚀 Optimized animation using transform
      function animateOptimized() {
        if (!animationRunning) return;
        
        const time = performance.now();
        const progress = (time % 4000) / 4000;
        
        // ✅ OPTIMIZED: Using transform (no reflow)
        const angle = progress * Math.PI * 2;
        const radius = 100;
        
        const x = Math.cos(angle) * radius;
        const y = Math.sin(angle) * radius;
        
        optimizedBox.style.transform = `translate(${x}px, ${y}px)`; // No reflow!
        
        optimizedFrameCount++;
        const currentTime = performance.now();
        if (currentTime >= lastOptimizedTime + 1000) {
          optimizedFPS = Math.round((optimizedFrameCount * 1000) / (currentTime - lastOptimizedTime));
          document.getElementById('optimizedFPS').textContent = optimizedFPS;
          optimizedFrameCount = 0;
          lastOptimizedTime = currentTime;
        }
        
        requestAnimationFrame(animateOptimized);
      }
      
      animateExpensive();
      animateOptimized();
    }
    
    function stopAnimations() {
      animationRunning = false;
    }
    
    function resetAnimations() {
      animationRunning = false;
      
      // Reset positions
      expensiveBox.style.left = '50%';
      expensiveBox.style.top = '50%';
      expensiveBox.style.transform = 'translate(-50%, -50%)';
      
      optimizedBox.style.transform = 'translate(-50%, -50%)';
      
      // Reset FPS counters
      expensiveFPS = 60;
      optimizedFPS = 60;
      document.getElementById('expensiveFPS').textContent = '60';
      document.getElementById('optimizedFPS').textContent = '60';
    }
    
    // Initialize
    resetAnimations();
  </script>
</body>
</html>

🚀 Performance Optimization Guide

✅ Cheap Operations

  • transform - GPU accelerated, no reflow
  • opacity - Cheap repaint, often hardware accelerated
  • filter - Modern browsers optimize well
  • will-change - Hint for browser optimization
  • backface-visibility - Triggers compositing

❌ Expensive Operations

  • width/height - Triggers reflow
  • margin/padding - Affects surrounding elements
  • top/left - Forces layout recalc
  • font-size - Can affect entire layout
  • offsetWidth/Height - Forces synchronous layout

💡 Pro Optimization Strategies

Code Patterns:

  • Batch DOM reads and writes
  • Use transform instead of top/left
  • Avoid layout thrashing
  • Use requestAnimationFrame for animations

Tools & Monitoring:

  • Chrome DevTools Performance panel
  • Layout Shift visualization
  • Paint flashing tool
  • Frame rate monitoring

Ready to Master Performance? 🚀

Experiment with repaints and reflows in real-time. See the dramatic performance difference between optimized and unoptimized code, and learn how to create buttery-smooth web experiences.

< PreviousNext >