Skip rendering offscreen content until the user scrolls to it — the browser does the lazy-rendering automatically. Two CSS properties replace IntersectionObserver boilerplate entirely.
content-visibility: auto on elements that are offscreen.
The browser skips their layout and paint until they enter the viewport.
contain-intrinsic-size provides a placeholder size so the scrollbar stays stable.
This replaces IntersectionObserver-based lazy rendering (~50–100 lines of JS) with 2 lines of CSS.
content-visibility: auto1000 list-group items rendered with content-visibility: auto; contain-intrinsic-size: auto 60px. Scroll down — offscreen items skip rendering until visible. Render time measured with Performance API.
content-visibility: auto100 cards with text content. Each card uses content-visibility: auto; contain-intrinsic-size: auto 400px. Offscreen cards skip layout/paint entirely.
content-visibility// Create observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList
.add('visible');
renderContent(entry.target);
observer.unobserve(entry.target);
}
});
},
{ rootMargin: '100px' }
);
// Observe all items
document.querySelectorAll('.lazy-item')
.forEach(item => {
observer.observe(item);
});
// Render function
function renderContent(el) {
el.innerHTML = buildContent();
el.style.minHeight = '';
}
// Cleanup on page unload
window.addEventListener('unload', () => {
observer.disconnect();
});
/* That's it. Two CSS properties. */
.item {
content-visibility: auto;
contain-intrinsic-size: auto 60px;
}
/* The browser automatically:
- Skips layout for offscreen items
- Skips paint for offscreen items
- Renders when scrolled into view
- Preserves scrollbar stability
- No JS, no observer, no cleanup */
Generate both lists above to see measured render times. content-visibility: auto reduces initial paint because offscreen elements are skipped.
| Test | Items | DOM Insert Time | CSS Applied |
|---|---|---|---|
| Long List | 1000 | — | content-visibility: auto |
| Card Grid | 100 | — | content-visibility: auto |
<style>
.list-item {
content-visibility: auto;
contain-intrinsic-size: auto 60px;
}
.card-item {
content-visibility: auto;
contain-intrinsic-size: auto 400px;
}
</style>
<!-- Just add the class. No JS needed. -->
<div class="list-item">...</div>
<div class="card-item">...</div>
<!-- The browser automatically skips rendering
for offscreen elements until they scroll
into view. Scrollbar stays stable thanks
to contain-intrinsic-size. -->