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>
const box = document.getElementById('expensiveBox');<br>
box.style.left = newLeft + 'px'; // Reflow!<br>
box.style.top = newTop + 'px'; // Reflow!<br>
box.offsetHeight; // Force reflow<br>
}
</div>
<div class="code-block good-code">
// đ OPTIMIZED: Uses compositor<br>
function animateOptimized() {<br>
const box = document.getElementById('optimizedBox');<br>
box.style.transform = `translate(${newX}px, ${newY}px)`;<br>
// 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.