CSS Performance Optimization
CSS Performance Optimization
CSS impacts both load time and runtime performance. Poorly optimized CSS can cause slow page renders, janky animations, and unnecessary repaints. This lesson covers techniques to optimize CSS delivery, reduce rendering costs, and improve visual performance through modern CSS features and best practices.
Critical CSS
Inline critical above-the-fold CSS to eliminate render-blocking:
<style>
/* Only styles for above-the-fold content */
.header { background: #333; padding: 1rem; }
.hero { min-height: 100vh; display: flex; }
.hero h1 { font-size: 3rem; color: #fff; }
</style>
<!-- Load remaining CSS asynchronously -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
CSS Containment
Use CSS containment to limit layout and paint scope:
.card {
contain: layout style paint;
/* Browser knows changes inside won't affect outside */
}
/* Strict containment for completely isolated widgets */
.widget {
contain: strict;
/* Equivalent to: layout style paint size */
}
/* Content containment for dynamic content */
.content-area {
contain: content;
/* Equivalent to: layout style paint */
}
/* Example: Optimizing a list */
.list-item {
contain: layout style;
/* Changes to one item don't trigger reflow of others */
}
content-visibility Property
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* Reserve space */
}
/* Example: Long article with sections */
<style>
.article-section {
content-visibility: auto;
contain-intrinsic-size: 0 400px;
}
</style>
<article>
<section class="article-section">...</section>
<section class="article-section">...</section>
<section class="article-section">...</section>
</article>
/* Result: Only visible sections are rendered */
/* Can reduce rendering time by 50-70% on long pages */
will-change Property
Hint to browser about upcoming changes for optimization:
.modal {
will-change: transform, opacity;
}
/* Apply before the change, remove after */
.element:hover {
will-change: transform;
}
.element:active {
transform: scale(1.1);
will-change: auto; /* Reset after animation */
}
/* JavaScript example */
const element = document.querySelector('.animate');
element.addEventListener('mouseenter', () => {
element.style.willChange = 'transform';
});
element.addEventListener('animationend', () => {
element.style.willChange = 'auto';
});
GPU Acceleration
Use transform and opacity for smooth animations:
.box {
transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s;
}
.box:hover {
width: 200px;
height: 200px;
top: 50px;
left: 50px;
}
/* Good: Uses GPU compositing */
.box {
transition: transform 0.3s, opacity 0.3s;
}
.box:hover {
transform: scale(1.5) translate(25px, 25px);
opacity: 0.9;
}
/* Force GPU acceleration with translateZ */
.animated {
transform: translateZ(0); /* Creates new compositing layer */
backface-visibility: hidden; /* Prevents flickering */
}
Reducing Repaints and Reflows
Minimize layout thrashing and unnecessary recalculations:
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const width = box.offsetWidth; // Read (triggers reflow)
box.style.width = width + 10 + 'px'; // Write (triggers reflow)
});
// Good: Batch reads and writes
const boxes = document.querySelectorAll('.box');
const widths = Array.from(boxes).map(box => box.offsetWidth);
boxes.forEach((box, i) => {
box.style.width = widths[i] + 10 + 'px';
});
// Use CSS classes instead of inline styles
// Bad: Forces style recalculation
element.style.width = '100px';
element.style.height = '100px';
element.style.background = 'red';
// Good: Single class change
element.classList.add('large-red-box');
// Use DocumentFragment for multiple DOM insertions
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
container.appendChild(fragment); // Single reflow
CSS-in-JS Performance
Optimize runtime CSS generation:
function Component({ color }) {
return (
<div style={{ backgroundColor: color, padding: '20px' }}>
Content
</div>
);
}
// Good: Extract static styles
const staticStyles = { padding: '20px' };
function Component({ color }) {
return (
<div style={{ ...staticStyles, backgroundColor: color }}>
Content
</div>
);
}
// Better: Use CSS classes with dynamic variables
const styles = `
.component {
padding: 20px;
background-color: var(--bg-color);
}
`;
function Component({ color }) {
return (
<div className="component" style={{ '--bg-color': color }}>
Content
</div>
);
}
Purging Unused CSS
Remove unused styles to reduce bundle size:
module.exports = {
content: [
'./src/**/*.html',
'./src/**/*.js',
'./src/**/*.jsx',
],
css: ['./src/styles/**/*.css'],
output: './dist/styles/',
safelist: [
'active', 'open', 'visible', // Dynamic classes
/^modal-/, // Regex patterns
]
};
// Tailwind CSS purge configuration
module.exports = {
purge: {
enabled: true,
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
options: {
safelist: ['bg-red-500', 'text-center'],
}
},
};
// Result: Can reduce CSS from 500 KB to 10-20 KB
Optimizing Selectors
body div.container ul li a.link { color: blue; }
/* Browser reads right-to-left, checks every anchor */
/* Good: Simple, specific selectors */
.nav-link { color: blue; }
/* Bad: Universal selector */
* { box-sizing: border-box; }
/* Good: Target specific elements */
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
/* Use BEM for flat specificity */
.card { }
.card__header { }
.card__title { }
.card--featured { }
/* Avoid expensive pseudo-selectors */
/* Bad */
:nth-child(n+1):nth-child(-n+10) { }
/* Good */
.item:nth-child(-n+10) { }
CSS Loading Strategies
<style>/* Critical CSS */</style>
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
<!-- Strategy 2: Media queries for conditional loading -->
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">
<!-- Strategy 3: Preload for high-priority resources -->
<link rel="preload" href="fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="critical.css" as="style">
<!-- Strategy 4: DNS prefetch for external CSS -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter">