---
title: "Website jumps while scrolling? Causes and fixes | AGR Studio"
description: "Why a site jumps while you scroll: layout shift or scroll jank, how to find the cause in Chrome DevTools, and fixes for images, fonts, headers and scripts."
url: https://agr.studio/blog/website-jumps-while-scrolling
language: en
---

[Blog](https://agr.studio/blog) 10 min read

# Why Your Website Jumps While You Scroll (and How to Fix It)

Category: Web

Updated: Oct 23, 2026

A client saw small jumps while scrolling their new site. How to find the cause in Chrome DevTools and fix images, fonts, banners, headers and scripts.

![Artyum Grebenyuk, founder of AGR Studio](https://cdn.sanity.io/images/coun0bog/production/4fbb7eca2741ad92fd54139f4e615213db80970f-352x352.png?w=160&q=82&auto=format) Artyum Grebenyuk Founder, AGR Studio

TLDR:

Small jumps while scrolling are usually layout shifts: an image, font, banner, sticky header or script changing size after the page has drawn. Find the moving element in the Layout shifts track of Chrome DevTools, then reserve its space in CSS or animate it with `transform`. If nothing moves but the page stutters, it's main-thread jank, not layout shift.

A website jumps while you scroll when something on the page changes size or position after it has been drawn: an image without reserved space, a web font swapping in, a banner pushing content down, or a script recalculating heights. If it happens after you stop scrolling, look first at lazy-loaded media, smooth-scroll and scroll-animation scripts recalculating, and the mobile browser bar resizing the viewport. Find the element that moves in Chrome DevTools, then reserve its space or move the change to `transform`.

An enterprise client noticed small jumps while scrolling their new site, "sometimes even after scrolling stops", and sent us a list of suspects. It was a good list. Here is each one: what it looks like, how to confirm it, and the fix.

## What counts as a layout shift?

A layout shift is a visible element changing position between two frames. Google measures it as Cumulative Layout Shift (CLS). Per [web.dev](https://web.dev/articles/cls):

-   A good CLS is 0.1 or less, measured at the 75th percentile of page loads, mobile and desktop separately. Above 0.25 is poor.
-   Shifts less than 1 second apart are grouped into windows of up to 5 seconds. Your CLS is the worst window.
-   Shifts within 500 ms of a tap, click or key press are excluded (`hadRecentInput`). Scrolling doesn't count as input, so a shift while scrolling counts in full.
-   Changes made with `transform` don't count, because they don't move the layout.

## Is it a layout shift or scroll jank?

Ask one question: did the content move, or did the scrolling stutter?

-   **Layout shift:** the line you were reading moves relative to the page. Something above it grew, shrank or appeared.
-   **Scroll jank:** the page freezes for a moment, then catches up. Nothing changed position; frames were dropped.

Jank comes from a busy main thread. In the [Performance panel](https://developer.chrome.com/docs/devtools/performance/reference), long tasks (over 50 ms) get a red triangle in the Main track and the Frames track marks dropped frames. The [Rendering tab](https://developer.chrome.com/docs/devtools/rendering/performance) adds live FPS and outlines elements with scroll-related listeners. The fixes: [passive](https://developer.chrome.com/docs/lighthouse/best-practices/uses-passive-event-listeners) touch and wheel listeners, [shorter tasks](https://web.dev/articles/optimize-long-tasks), and no layout reads (`getBoundingClientRect`, `offsetHeight`) in scroll handlers. Heavy video is another common cause; see [video-heavy website performance](https://agr.studio/blog/video-heavy-website-performance).

## How do you find what is shifting?

1.  Open the DevTools Performance panel. The [live metrics view](https://developer.chrome.com/docs/devtools/performance/overview) shows your local CLS and a Layout shifts log as you scroll. It replaced the Web Vitals extension, which Chrome [retired in January 2025](https://developer.chrome.com/blog/web-vitals-extension-merged).
2.  Record a trace while you scroll. In the Layout shifts track, each shift is a purple diamond. Click one and the Summary tab lists the elements, the score and potential culprits.
3.  In the Rendering tab, tick Layout Shift Regions. Areas that shift flash purple.
4.  Or log shifts in the console:

```
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.hadRecentInput) continue;
    for (const { node, previousRect, currentRect } of entry.sources) {
      console.log(entry.value.toFixed(4), node, previousRect, currentRect);
    }
  }
}).observe({ type: 'layout-shift', buffered: true });
```

The element reported is often the victim, not the cause; [web.dev](https://web.dev/articles/debug-layout-shifts) suggests checking the element just before it. A large downward shift usually means something was inserted. A shift of 1 or 2 pixels usually points to a web font or conflicting CSS.

## Why does Lighthouse say CLS is fine when users see jumps?

Lighthouse is a lab test: by default it only captures shifts during load. Field data from the Chrome UX Report (CrUX) covers the whole visit, scrolling included. Search Console's [Core Web Vitals report](https://support.google.com/webmasters/answer/9205520) uses CrUX data from the last 28 days. PageSpeed Insights shows lab and field side by side. If field CLS is much worse, the shifts happen after load.

## Do images, videos and embeds reserve their space?

**Looks like:** text drops as an image, video or iframe arrives.

**Confirm:** Lighthouse lists images without explicit width and height. In a trace, the shifted elements sit right under the media.

**Fix:** give every image and video `width` and `height` attributes. Browsers work out the aspect ratio from them before the file loads. Use `aspect-ratio` for embeds.

```
<img src="team.jpg" width="1600" height="900" alt="The team at work">
```

```
img, video { max-width: 100%; height: auto; }
.embed { aspect-ratio: 16 / 9; width: 100%; }
.embed iframe { width: 100%; height: 100%; }
```

When we rebuilt agr.studio, CMS images came without dimensions. We now read the size from each asset and write it into the markup.

## Are web fonts reflowing your text?

**Looks like:** headings re-wrap a moment after load, or every line nudges by a pixel or two.

**Confirm:** throttle the network in DevTools and reload. Shifts that line up with font requests are font swaps.

**Fix:** per [web.dev](https://web.dev/articles/font-best-practices), `font-display: swap` shows text fastest but can shift when the font arrives. `optional` waits up to 100 ms and never swaps, so a first-time visitor may see the fallback. Preload the one or two files used above the fold ([`crossorigin` is required](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/preload), even on your own domain). Then size the fallback like the web font with [`size-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@font-face/size-adjust) and the metric overrides.

```
<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>
```

```
/* Example values: compute yours from the font's metrics */
@font-face {
  font-family: "Brand Fallback";
  src: local("Arial");
  size-adjust: 97%;
  ascent-override: 90%;
  descent-override: 33%;
  line-gap-override: 0%;
}
body { font-family: "Brand", "Brand Fallback", sans-serif; }
```

[Chrome's guide to font fallbacks](https://developer.chrome.com/blog/font-fallbacks) explains the maths; `next/font` and Fontaine generate these rules for you. Safari supports `size-adjust` but not the three overrides yet.

Scripts that split text into lines for animation should wait for `document.fonts.ready`. Split earlier and the lines change when the font arrives.

### Why do line heights differ between browsers?

Some font files set a flag called [USE\_TYPO\_METRICS](https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection), which picks the set of vertical metrics used for line spacing. Browsers don't treat it the same way. In our work on agr.studio, Chrome on Windows and Android and Firefox honoured it and Safari ignored it, so the same text sat about 0.06em higher in some browsers. We pinned `ascent-override`, `descent-override` and `line-gap-override` on the web font's own `@font-face` to the font's hhea values, the ones Safari already used. The font files stay untouched, and the line boxes match everywhere.

## Is something being inserted above the content?

**Looks like:** the page drops by the height of a bar, a second or two after load. Cookie banners, announcement bars, late embeds and ads are the usual sources.

**Confirm:** a large downward shift, with the inserted element just above the shifted one.

**Fix:** overlay banners instead of pushing content. Reserve space for late bars and ad slots, and don't collapse a slot when nothing fills it ([optimize CLS](https://web.dev/articles/optimize-cls)).

```
.cookie-banner { position: fixed; inset: auto 0 0; }
.announcement-bar { min-height: 44px; }
```

Scroll anchoring hides some of this: the browser adjusts the scroll position when content is inserted above the viewport. Chrome and Firefox have it, and [WebKit announced it for Safari 27](https://webkit.org/blog/17967/news-from-wwdc26-webkit-in-safari-27-beta/). Older Safari still jumps, and anchoring does nothing for content in view. It's a safety net, not a fix.

## Do sections change height after load?

**Looks like:** a slider shows every slide stacked, then snaps into one row. A component sized by JavaScript changes height at a breakpoint. On a phone, a full-height hero grows or shrinks when the browser bar hides, often just as you stop scrolling.

**Confirm:** throttle the CPU and reload, then resize the window. Test on a real phone.

**Fix:** give the server-rendered markup its final size in CSS before any script runs. Style a carousel's uninitialised state like the initialised one. Use media queries, not JavaScript, for breakpoints.

Mind the [viewport units](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/length). `vh` currently equals `lvh`, the viewport with the browser bars hidden, so a `100vh` hero is taller than the screen while the bars show. `svh` assumes the bars are showing. Both stay fixed; `dvh` follows the bars, and MDN warns it can resize content while the user scrolls.

```
.hero { min-height: 100vh; min-height: 100svh; }
```

If you use `content-visibility: auto`, add `contain-intrinsic-size: auto 800px`. Without a size estimate, skipped sections collapse and the scrollbar jumps ([web.dev](https://web.dev/articles/content-visibility)).

## Should wrappers use overflow: hidden or overflow: clip?

Use `clip` when a wrapper only needs to cut off overflow, such as an animation creating sideways scroll. Per [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/overflow), `hidden` makes the element a scroll container when content overflows. It can still scroll, by script or when focus moves inside it. Two side effects:

-   A sticky element sticks to its nearest ancestor with `overflow` set to `hidden`, `scroll`, `auto` or `overlay`, even if that ancestor never scrolls ([MDN on position](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/position)). Your sticky header or sidebar stops sticking.
-   Smooth-scroll scripts and scroll-driven animations can measure against that accidental scroll container instead of the page.

`clip` hides overflow without creating a scroll container, and nothing can scroll it. One caveat: focusable content cut off by `clip` won't scroll into view, so don't clip interactive content.

```
main { overflow-x: clip; }
.marquee { overflow: clip visible; }
```

We use the first rule on agr.studio.

## Does your sticky header change height on scroll?

**Looks like:** the page jumps by a header's height as you pass a point. Usually the header switches to `position: fixed` after you scroll (it leaves the flow, so content jumps up) or shrinks from 96px to 64px.

**Confirm:** turn on Layout Shift Regions and scroll slowly past the point where the header changes.

**Fix:** make the header sticky from the first frame, with a constant layout height. Shrink or hide it with `transform`, which doesn't shift layout.

```
.site-header { position: sticky; top: 0; height: 72px; transition: transform 0.3s; }
.site-header.is-hidden { transform: translateY(-100%); }
```

## Is a smooth-scroll library or scroll animation recalculating?

**Looks like:** pinned sections jump into place, animations restart from a new position, or the page settles a moment after you stop. Smooth-scroll libraries keep easing after your input ends, so anything tied to scroll position fires "after scrolling stops".

**Confirm:** disable the library and scroll again. If the jumps go, it's the library or its triggers.

**Fix:** GSAP's ScrollTrigger recalculates start and end positions on resize, load and a few other events. If content changes height in between, positions are wrong until the next refresh, and then pinned sections jump. Call `ScrollTrigger.refresh()` after anything changes height. On phones the browser bar changes the viewport height; GSAP's [`ignoreMobileResize`](https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.config%28%29/) stops small vertical resizes on touch-only devices from triggering a refresh, which GSAP says avoids those jumps. [Lenis](https://github.com/darkroomengineering/lenis) resizes itself with a ResizeObserver by default and runs on GSAP's ticker:

```
gsap.registerPlugin(ScrollTrigger);
ScrollTrigger.config({ ignoreMobileResize: true });

const lenis = new Lenis();
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
```

Then check what the animations change. [Animate `transform` and `opacity`](https://web.dev/articles/animations-guide), not `top`, `height`, `margin` or `letter-spacing`; layout properties move the content around them. On agr.studio, a headline animation that tightened letter-spacing re-wrapped lines mid-animation, so we moved it to a transform. With the image and font fixes above, our measured CLS went from 0.19 to 0.

## Do lazy-loaded images or hydration swap content in?

**Looks like:** content jumps exactly when you scroll into a new area, or the page reshuffles once JavaScript finishes.

**Confirm:** shifts in the trace line up with image requests or with the framework's hydration.

**Fix:** `loading="lazy"` defers an image until it's near the viewport. Without `width` and `height` it starts at 0×0 and expands while the user scrolls, and that shift counts ([web.dev](https://web.dev/articles/browser-level-image-lazy-loading)). Size every lazy image, and don't lazy-load the first viewport. Script-based lazy-loaders need placeholders with the final aspect ratio.

Hydration jumps happen when the browser renders something different from the server HTML: a client-only component, personalised content, a date. Render the same markup on both sides, or reserve the space.

## Checklist: symptom, cause, fix

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Text drops as media appears | Images, video or iframes without dimensions | width and height attributes, aspect-ratio |
| Headings re-wrap after load | Web font swap | Matched fallback, preload, or font-display: optional |
| Line heights differ by browser | USE\_TYPO\_METRICS handled differently | Pin metrics with @font-face overrides |
| Page drops by a bar's height | Banner, ad or embed inserted above | Overlay it or reserve the space |
| Section snaps to a new height | Script-set heights, carousel setup | Final size in CSS before scripts run |
| Hero resizes during mobile scroll | 100vh or dvh with browser bars | svh or lvh |
| Sticky element stops sticking | overflow: hidden on an ancestor | overflow: clip |
| Jump when passing a scroll point | Header changes height or position | Sticky from the start, transform to hide |
| Jump after scrolling stops | Smooth scroll or ScrollTrigger refresh | Refresh after height changes, ignoreMobileResize |
| Jump when entering a new area | Lazy images without dimensions | Size every lazy image |
| Reshuffle after JavaScript loads | Hydration mismatch | Same markup on server and client |
| Stutter, nothing moves | Long tasks, blocking scroll listeners | Passive listeners, shorter tasks |

## Notes for Framer, Webflow and custom code

-   **Framer:** [Clip is now the default Overflow setting](https://www.framer.com/help/articles/overflow-clip/) for new layers, replacing Hidden. Layers set to Hidden, in older projects or by hand, can still break sticky elements; switch them to Clip.
-   **Webflow:** the Overflow property under Style panel, Size [supports `clip`](https://webflow.com/updates/additional-sizing-and-layout-controls). Webflow suggests it when sticky elements misbehave with `hidden`.
-   **Custom code:** every cause above is in your hands. On Next.js, `next/font` generates matched fallbacks; Fontaine does the same elsewhere.

On any platform, check embeds and custom code blocks first. In our experience, that's where reserved space most often goes missing.

If your site jumps and you can't see why, send it for our free [design and SEO check](https://agr.studio/design-check); speed is part of it. For ongoing fixes by one team, there's [Website Partner](https://agr.studio/services/website-partner).

## FAQ

### Why does my website jump after I stop scrolling?

Usually something recalculates once scrolling settles. Smooth-scroll libraries keep easing after your input ends, scroll-animation tools such as GSAP ScrollTrigger refresh their positions when the viewport resizes, and on phones the browser bar showing or hiding changes the viewport height, which resizes anything sized in `vh` or `dvh`. Lazy-loaded images that arrive without reserved space do the same. Record a trace in the Chrome DevTools Performance panel to see which element moves.

### What is a good CLS score?

Google counts a Cumulative Layout Shift of 0.1 or less as good and above 0.25 as poor, measured at the 75th percentile of page loads, separately for mobile and desktop. CLS is the worst burst of shifts on the page: shifts less than a second apart, grouped into windows of up to 5 seconds. Check your real-user score in Search Console's Core Web Vitals report or PageSpeed Insights.

### Do layout shifts while scrolling count toward CLS?

Yes. Only shifts within 500 ms of a discrete input, such as a tap, click or key press, are excluded. Scrolling, dragging and pinch-zoom don't count as input, so an image or banner that pushes content while someone scrolls adds to CLS in full.

### Why does Lighthouse show a good CLS when users still see the page jump?

Lighthouse is a lab test that only captures shifts during the initial page load. Real-user data from the Chrome UX Report covers the whole visit, including shifts caused by scrolling, lazy loading and late scripts. If field CLS in PageSpeed Insights or Search Console is much worse than the lab score, the shifts happen after load.

### Should I use overflow: hidden or overflow: clip?

Use `overflow: clip` when you only want to cut off overflowing content. `overflow: hidden` turns the element into a scroll container when content overflows, which can stop `position: sticky` working for elements inside it and confuse scroll scripts. `clip` creates no scroll container, but don't use it on wrappers that cut off focusable content, because that content won't scroll into view.

### How do I stop web fonts from causing layout shift?

Match the fallback font to the web font with `size-adjust`, `ascent-override`, `descent-override` and `line-gap-override` on a `local()` fallback, and preload the one or two font files used above the fold. `font-display: optional` removes font-swap shifts entirely, at the cost of some first-time visitors seeing the fallback. Tools such as `next/font` and Fontaine generate the matched fallback for you.

Blog

## Continue reading our latest blogs

[See all articles](https://agr.studio/blog)

-   ![Illustration: a shortlist of agencies, the reviewed ones ticked](https://cdn.sanity.io/images/coun0bog/production/e582c831242e586865b0fe3dc1232c51a6a654d1-2400x1800.jpg?w=1000&q=82&auto=format)
    
    Guides 11 min read
    
    ### 8 Best Astro Agencies for B2B Websites (2026)
    
    Read article
    
    → https://agr.studio/blog/best-astro-agencies-b2b-2026
    
-   ![Illustration: a shortlist of agencies, the reviewed ones ticked](https://cdn.sanity.io/images/coun0bog/production/da64ccc8781100392805d435b09b33ce25e848a1-2400x1800.jpg?w=1000&q=82&auto=format)
    
    Guides 10 min read
    
    ### 8 Best Next.js Agencies for B2B Websites (2026)
    
    Read article
    
    → https://agr.studio/blog/best-nextjs-agencies-b2b-2026
