CLS (Cumulative Layout Shift) Optimization for 1C-Bitrix
CLS measures visual instability of a page: how much elements shift during loading. Google's threshold is CLS < 0.1. Shifts that cause users to miss a button or lose their reading position are poor UX and a ranking factor.
On Bitrix sites, CLS typically ranges from 0.2–0.6. The culprits are predictable and eliminated systematically.
How CLS is Calculated
CLS = sum of (impact fraction × distance fraction) for each shift. Impact fraction is the fraction of the viewport affected by the shift. Distance fraction is the fraction of the viewport the element moved. An image taking up 30% of the viewport height shifted down by 20% of the viewport height yields 0.06 for a single incident.
Measurement: Chrome DevTools → Performance → record + scroll the page. The Layout Shift section shows each shift with the affected element indicated.
Images Without Width and Height
The most common cause of CLS in Bitrix. When the browser parses <img src="..."> without dimensions, it doesn't know how much space to reserve. Content below the image renders without the offset, then shifts when the image loads.
Standard Bitrix component templates often output images without size attributes. Fix in the component template:
// Get image dimensions during rendering
$imgFile = \CFile::GetByID($arItem['PREVIEW_PICTURE'])->Fetch();
$width = $imgFile['WIDTH'] ?: 300;
$height = $imgFile['HEIGHT'] ?: 300;
echo '<img src="' . \CFile::GetPath($arItem['PREVIEW_PICTURE']) . '"'
. ' width="' . (int)$width . '"'
. ' height="' . (int)$height . '"'
. ' loading="lazy"'
. ' alt="' . htmlspecialchars($arItem['NAME']) . '">';
CSS aspect-ratio as an alternative — when dimensions are known and fixed in advance:
.product-card__image-wrapper {
aspect-ratio: 4 / 3;
overflow: hidden;
}
.product-card__image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
}
The space is then reserved through CSS and specific dimensions in HTML are optional.
Dynamic Blocks: Cart, Counters, Banners
Bitrix sites often have dynamic blocks in the header: item count in the cart, total, notification count. This data loads via AJAX after rendering. If there's content below the cart — it shifts when the cart data loads.
Space reservation through CSS:
.header__cart-count {
min-width: 24px;
min-height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
}
Set fixed dimensions for all dynamic header and sidebar elements. An empty element reserves space, and after data loads, the size doesn't change.
Skeleton loading instead of an empty block: shows gray placeholders of the appropriate size until data loads. Doesn't eliminate CLS but improves perception.
Advertising and Banner Blocks
Ad blocks are a classic CLS source. The block loads via JS, inserts into the DOM, and shifts content. Solution — always reserve space for ads:
.ad-block {
min-height: 90px; /* standard banner height */
width: 100%;
}
If banner size is unpredictable — use a fixed-size container with overflow: hidden.
Fonts and FOIT/FOUT
Without font-display settings, web fonts cause two effects: FOIT (Flash of Invisible Text) — text is hidden until the font loads, or FOUT (Flash of Unstyled Text) — text first appears in a system font, then redraws with the custom font.
FOUT causes CLS if the system and custom fonts have different metrics (character size, line height). Solutions:
@font-face {
font-family: 'SiteFont';
src: url('/bitrix/fonts/sitefont.woff2') format('woff2');
font-display: swap; /* FOUT instead of FOIT */
font-style: normal;
font-weight: 400;
}
To minimize CLS from FOUT — font-display: optional (font is used only if already cached) or select a system fallback font with similar metrics via CSS size-adjust:
@font-face {
font-family: 'SiteFontFallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 90%;
}
Animations and Transforms
Animations via top, left, margin change geometry and cause reflow — the browser recalculates element positions, which is recorded as CLS. Use only transform and opacity for animations:
/* Causes CLS — changes geometry */
.banner { transition: top 0.3s; }
/* Doesn't cause CLS — compositing only */
.banner { transition: transform 0.3s; }
.banner.hidden { transform: translateY(-100%); }
Callback Widgets and Chatbots
Widgets like JivoSite, CallTouch, Bitrix24 chat load JS asynchronously and insert a button into the DOM. If the button appears over content — CLS occurs. Options:
- Reserve space for the widget via CSS placeholder
- Use
position: fixedwithbottom: 20px; right: 20px— fixed elements don't cause CLS - Defer widget loading to
idleviarequestIdleCallback
CLS Optimization Timeline
| Task | Time | Impact |
|---|---|---|
| Adding width/height to images in component templates | 1–3 days | CLS −0.1–0.3 |
| Reserving space for dynamic blocks | 1 day | CLS −0.05–0.15 |
| Setting font-display | 0.5 day | CLS −0.02–0.1 |
| Fixing ad blocks | 0.5 day | CLS −0.05–0.2 |
| Converting animations to transform | 1–2 days | CLS → 0 from animations |
After fixes — check PageSpeed Insights using real data. CrUX updates with a 28-day delay — changes in actual traffic metrics visible in a month.







