Lazy Loading Configuration for 1C-Bitrix
On a catalog page with 48 product cards, the browser by default loads all 48 images when the page opens — including those outside the visible viewport. With an average product image weight of 80–150 KB, this amounts to 4–7 MB of unnecessary traffic on the first load, which slows down Largest Contentful Paint and Time to Interactive.
Native Lazy Loading
The most modern and simplest approach is the loading="lazy" attribute on <img> tags. Browsers from Chrome 77, Firefox 75, and Safari 15.4 onwards support it natively:
<img src="/upload/product.jpg" loading="lazy" width="300" height="300" alt="...">
The width and height attributes are required — without them the browser cannot reserve space for the image before it loads, which causes layout shift (the CLS metric).
In Bitrix component templates, this is added to the template.php of the catalog.element and catalog.section components:
<img src="<?= $item['PREVIEW_PICTURE']['SRC'] ?>"
loading="lazy"
width="<?= $item['PREVIEW_PICTURE']['WIDTH'] ?>"
height="<?= $item['PREVIEW_PICTURE']['HEIGHT'] ?>"
alt="<?= htmlspecialchars($item['NAME']) ?>">
For above-the-fold images (hero, first 2–4 cards in the grid), loading="lazy" should not be used — it would delay their loading and worsen LCP.
JavaScript Implementation for Older Browsers and Custom Scenarios
If support for Safari prior to 15.4 is needed, or non-standard behavior is required (e.g., a preloader during loading), use IntersectionObserver:
const lazyImages = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
}, { rootMargin: '200px' });
lazyImages.forEach(img => observer.observe(img));
rootMargin: '200px' — the image starts loading 200px before it enters the viewport. This prevents flickering during fast scrolling.
Lazy Loading for CSS Background Images
CSS background images (the background-image property) are not supported by native lazy loading. The solution is to add a loaded class via IntersectionObserver and only define the background for elements with that class:
.banner { background-color: #f0f0f0; } /* placeholder */
.banner.loaded { background-image: url('/upload/banner.jpg'); }
Case Study and Results
An online store with a catalog page displaying 60 products (3 columns × 20 rows, most below the fold). After adding loading="lazy" to all cards except the first row: the volume of images loaded on the first visit dropped from 6.2 MB to 1.1 MB. LCP decreased from 3.8 s to 1.9 s. During scrolling, users notice no delay — images load in advance thanks to rootMargin.
Setting up lazy loading takes 4–8 hours: auditing component templates, adding attributes, testing on real devices (it is important to verify the absence of CLS on mobile). Projects with SSR or non-standard sliders will require additional configuration for specific components.







