Scroll✨ Premium

Zoom & Blur

Three symmetrical keyframes on animation-timeline: scroll() inside a 280 px box: the circle is sharp, whole and at its 200 px only halfway through (260 px out of 520) — ×0.5, 10 px blur, .3 opacity before; ×2, 10 px blur, .3 after. A focus point, not a zoom. Zero JavaScript.

CSSZoomBlur

Updated

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Scroll has 70 effects, including 12 free. Explore the category →

3 usage examples

Featured lot of an auction — catalogue page of an auction house — Zoom & Blur example 1

① Featured lot of an auction — catalogue page of an auction house

WhenSale page: the house's name and the date in the bar, estimate and premium at the top of the entry, then the lot's photo going from small-and-blurred to full-and-sharp in the center of the frame and growing away toward the provenance, the size and the bid button.
WhyThe buyer sees the work sharp at a single scroll position: they stop there, as in front of the window — fx-0590 keeps growing and offers the eye no resting position. Since the sharp state is the base state, the entry stays readable on Firefox and under prefers-reduced-motion.
SettingsSold code untouched (animation-timeline: scroll() on .sdc-zoom-element); 48 px bar, 312 px box, the stage replaced by a column of 250 px text block · 200 px painting · 250 px text block, 16 px gaps: 732 px of content, 420 travel, sharp at 210; the circle becomes a gilded frame (border-radius: 3px, gradient, inset box-shadow) around a seascape made of gradients; texts in z-index: 1 — at the end of the travel the ×2 painting, blurred at 30%, fades behind the provenance. Ivory background, serif.
Specimen under the microscope — biology module of an online course — Zoom & Blur example 2

② Specimen under the microscope — biology module of an online course

WhenBiology lesson: module and progress in the bar, an instruction — scroll as you would turn the fine-focus knob —, then the onion epidermis slide, blurred and small, sharp and full-size when the wheel reaches the right focus, blurred again while growing over the list of things to spot.
WhyThis is exactly a microscope's fine-focus knob: sharp at one position, blurred on either side — the third keyframe blurs the slide again as soon as the student overshoots the point, and that overshoot is what teaches them there is a setting to find. fx-0551 Blur to Clear only does the first half-movement and stays sharp afterwards: the focus cannot be missed, so there is nothing to adjust.
Settings314 px box under a 46 px bar, same 250 · 200 · 250 column (418 travel, sharp at 209); the circle keeps its border-radius: 50% and becomes the eyepiece's field: two repeating-linear-gradient for the cell walls (38 × 21 px cells), seven nuclei as spans, black rim in box-shadow and inset vignetting; light background, sans-serif, #2563eb accent.
Ring on a jeweler's product page — the piece comes into focus between two paragraphs — Zoom & Blur example 3

③ Ring on a jeweler's product page — the piece comes into focus between two paragraphs

WhenSolitaire page: the story of the stone as the first paragraph, then the ring coming into focus in the middle of the frame, then metal, stone, size, price and the appointment button, under the piece fading away as it grows.
WhyThe sharp moment is exactly mid-travel: the writer places two paragraphs on either side and knows where the eye will land — fx-0580 Zoom on Scroll grows to ×5 without ever becoming discreet again.
Settings310 px box under a 50 px bar, 422 travel, sharp at 211; the circle loses its background (background: none) and holds the band (150 px, border: 16px gold in four shades) and the oval sapphire (68 × 60 px, radial-gradient, ::after highlight); details in a 2 × 2 grid; #0f0d0b background, serif, #d4b15f gold.

How it works

Four elements: the frame .demo-preview (centered flex, min-height: 300px, position: relative, #0a0a0f background, white text, sans-serif), the box .sdc-scroll-container (100% × 280px, overflow-y: auto, scroll-behavior: smooth, a 4 px scrollbar via ::-webkit-scrollbar), the stage .sdc-zoom-content (800 px, centered flex) and the circle .sdc-zoom-element: 200 × 200 px, border-radius: 50%, 135° gradient #8b5cf6 → #ec4899, no text. Two hard-coded labels: .sdc-zoom-text “Zoom in → net → zoom out” (11.5 px, white at 30%, 40 px from the bottom of the stage) and .sdc-scroll-hint “Scrollez ↓” (10.4 px, anchored to the frame, pulsing .3 → .7 over 2 s). Travel: 800 − 280 = 520 px. Empty JavaScript field, no var(--…), no id; measured in a blank page (white and dark background, 1,280 and 390 px): no request, no console error in Chromium 151, WebKit 26.5 and Firefox 153.

One rule does everything: animation-name: sdcZoomBlur with animation-timeline: scroll() — the animation no longer advances with time but with the position of the nearest scrolling ancestor, the box; animation-duration: auto and animation-range: normal spread it over the whole travel, linear joins the keyframes in straight lines. Three keyframes: 0% { transform: scale(.5); filter: blur(10px); opacity: .3 }, 50% { transform: scale(1); filter: blur(0); opacity: 1 }, 100% { transform: scale(2); filter: blur(10px); opacity: .3 }. Measured in 100 px steps: ×0.69 / 6.2 px / .57, ×0.88 / 2.3 px / .84, ×1.15 / 1.5 px / .89, ×1.54 / 5.4 px / .62, ×1.92 / 9.2 px / .35, then ×2 / 10 px / .3; at exactly 260 px, ×1 / 0 / 1. Blur and opacity change at the same rate on both sides (3.8 px and .27 per 100 px), the scale does not: +0.19 per 100 px before the sharp point (100 → 200 px), +0.38 after (200 → 400 px). The sharp state exists at a single position, and it is also the element's base state: wherever the timeline does not exist, that is what shows.

The circle is centered 400 px from the top of a stage whose box shows 280: at rest, shrunk to 100 px (350–450 px), it is below the fold — an empty dark box, plus the hint. Measured: it enters from the bottom at 59 px of scrolling, is centered and sharp at 260 px with 40 px margins, grows and leaves through the top — at 520 px its 400 px span from −320 to 80 px, an 80 px blurred cap at 30% remains, and the label shows only over the last 41 pixels. Sharp point and centering are tied: the 50% keyframe falls at mid-travel, the stage puts the circle at mid-height, hence in the center of the box at that moment; moving one without the other gives a sharp object off frame. Width-wise, ×2 is 400 px: in a box narrower than 400 px, overflow-y: auto (which implies overflow-x: auto) opens a horizontal track — measured 13 px at 390 px wide. scroll-behavior: smooth only eases positions set by script or anchor. With no id and no script, several copies per page each drive their own box.

Accessibility

  • prefers-reduced-motion not in the code: measured under emulation, same values (×0.5 → ×2, 10 px blur), hint still pulsing. Nothing is hidden — circle visible from 59 px, sharp at 260 — and nothing moves without the reader's gesture (WCAG 2.3.3). The sensitive part is the blur: 10 px on real content makes it unreadable over two thirds of the travel. Add @media (prefers-reduced-motion: reduce) { .sdc-zoom-element { animation: none } .sdc-scroll-hint { animation: none } }: the circle then stays in its base state, 200 px, sharp, opaque — what Firefox shows.
  • Keyboard: measured, Tab focuses the box without a tabindex in Chromium (outline: auto 1px ring) and Firefox 153; Arrow Down = 40 px (51 in Firefox), Page Down and Space = 245 (252), End = 520, Home = 0. WebKit 26.5 does not focus it without tabindex="0" (focus on body, arrows inert; 40 px per arrow with the attribute): add it, with a :focus-visible and an aria-label. No keystroke stops on the sharp point (245 then 490 px): from the keyboard you never see the object sharp — see “Holding the sharp point”.
  • Contrast: the hint “Scrollez ↓” (10.4 px, white at 30% on #0a0a0f) reads 2.6:1 static, 1.2:1 to 1.8:1 under the pulse (the .3 → .7 opacity multiplies the color's 30%); the label (11.5 px, 30%) stays at 2.6:1. Decorative: aria-hidden="true", or 70% (9.7:1). The circle carries no text; readable content is sharp only around 260 px, the blur exceeds 5 px before 130 and after 390 — write the information outside the circle as well.
  • Screen readers and fallback: the circle is an empty div, nothing to announce; if it becomes a meaningful image (lot, ring, slide), give it role="img" and an aria-label — a screen reader does not focus the lens. Both labels are read as is, in French and Franglais. Without animation-timeline (Firefox, Chrome before 115), the circle is at 200 px, sharp, opaque, and scrolls with the stage: everything stays visible and reachable, without the effect.

Browser compatibility

CSS only: animation-timeline: scroll(), animation-duration: auto, animation-range (Scroll-driven Animations), filter: blur(), transform: scale(), opacity, overflow-y: auto, scroll-behavior, flexbox, @keyframes. Zero JavaScript, zero dependencies. Chrome and Edge 115+, Safari and iOS 26+ (measured in WebKit 26.5: same values as Chromium). Firefox: no version. Measured in Firefox 153: CSS.supports() returns false for animation-timeline: scroll(), animation-duration: auto and animation-range, all three declarations are dropped; the animation lasts 0 s with no fill (animation-fill-mode: none) and the element falls back to its base state: transform: none, filter: none, opacity: 1. The Firefox visitor sees a 200 px circle, sharp and opaque, crossing the box like ordinary content — in at 20 px, centered at 260, out at 500 — then the label. Nothing hidden, nothing missing: no effect. No @supports in the sold code; @supports not (animation-timeline: scroll()) { .sdc-zoom-element { animation: none } } would render exactly the same.

Chrome 115+✓ Full
Firefox (all versions)✓ No effect: sharp 200 px circle, all content visible
Safari 26+✓ Full
Edge 115+✓ Full
Mobile iOS 26+✓ Full (touch scrolling of the box)
Android Chrome 115+✓ Full

Without animation-timeline (Firefox, Chrome and Edge before 115, Safari before 26): a 200 px circle, sharp, opaque, scrolling with the stage — everything visible, nothing moves. Without filter, zoom and fade remain. Where overflow is not honored (mail clients), the 800 px stage shows whole, sharp circle in the middle.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="sdc-zoom-content">
      <div class="sdc-zoom-element"></div>
      <span class="sdc-zoom-text">…</span>
    </div>
  </div>
  <span class="sdc-scroll-hint">…</span>
</div>
Unlock the full code — €9.90/mo excl. tax

The full HTML + CSS + JS of Zoom & Blur, ready to paste — plus all 811 effects. Or €299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Keyframes (CSS — @keyframes sdcZoomBlur) ×0.5 / 10 px / .3 → ×1 / 0 / 1 → ×2 / 10 px / .3 Three independent values: scale(.85) → 1 → 1.25 for a discreet focus, blur(4px) for a more photographic blur, opacity: .6 to keep the object present at both ends. Keep the 50% keyframe at ×1 / 0 / 1: it is the base state, the one Firefox and reduced-motion show.
Position of the sharp point (CSS — the 50% keyframe, and the stage centering) 50% = 260 px, circle centered 400 px into the stage Measured with the keyframe at 35%: sharp at 182 px, but the circle at 118–318 px of the box, not centered (center at 218 instead of 140). Object center = travel × fraction + 140 px, i.e. 322 px for 35% — padding-top on the stage or a negative margin-top on the circle.
Holding the sharp point (CSS — two keyframes 40%, 60%) none: sharp at a single position Measured with 40%, 60% { transform: scale(1); filter: blur(0); opacity: 1 }: sharp from 208 to 312 px, 104 px of travel instead of one position — an arrow-key step (40 px) can no longer miss it. The blur resumes at 3.75 px 130 px on either side.
Travel and range (CSS — .sdc-zoom-content { height: 800px }, .sdc-scroll-container { height: 280px }, animation-range) 520 px, animation-range: normal Travel = stage − box; the sharp point stays at mid-travel. A 1,200 px stage = 920 px of travel, motion twice as slow; a taller box shows the circle sooner (at 280 px, it only enters after 59 px). animation-range: 20% 80% concentrates the animation in the middle, but outside the range the element falls back to its base state (measured: 200 px sharp at 65 px, jump to 100 px blurred at 104) — add animation-fill-mode: both.
Pace (CSS — animation-timing-function: linear) linear Applies between each pair of keyframes. Measured with ease-in-out: ×0.96 / 0.8 px / .94 at 208 px (against ×0.9 / 2 px / .86 in linear), ×1.08 at 312 — the circle lingers near the sharp point and rushes toward the ends.
Size, shape and content (CSS — .sdc-zoom-element { width/height: 200px; border-radius: 50% }) 200 px, circle, #8b5cf6 → #ec4899 gradient All content (img, SVG, children) is zoomed and blurred with the box: a 200 px image is stretched to 400 px at the end — provide a 400 px source. In a box narrower than 400 px, horizontal overflow (measured 13 px at 390 px): overflow-x: hidden. With no id, several instances per page work.
Scroller and texts (CSS — scroll(), scroll-behavior: smooth, .sdc-scroll-hint, .sdc-zoom-text) nearest scroller, smooth, “Scrollez ↓” and “Zoom in → net → zoom out” scroll() takes the nearest scrolling ancestor; scroll(root) drives from the page, view() from the crossing of the viewport (see the FAQ). smooth only eases positions set by script or anchor. Both texts are hard-coded (the label in Franglais, visible over the last 41 pixels): translate them or delete both spans.

FAQ

Because sharp point and centering are tied: the 50% keyframe falls at mid-travel (260 px) and, for the circle to be in the center of the box at that moment, it must sit at mid-height of the stage (400 px out of 800) — hence, at rest, at 350–450 px shrunk to 100 px, below the 280 visible pixels. Measured: entry from the bottom at 59 px, a 49 px blurred cap at 100, whole and sharp at 260. Three remedies: a taller box (400 px) shows it from the start; content above it (the three scenes on this page put 250 px of text before the object) turns its blurred entry into an announcement; animation-range: 20% 80% with animation-fill-mode: both keeps it at ×0.5 blurred until it is inside the box. The trap: moving the circle up without touching the keyframes — sharp at 260 px, but 140 px too high, cut by the edge.
Same skeleton: 280 px box, 800 px stage, animation-timeline: scroll(), linear, animation-range: normal, no JavaScript, same fate on Firefox. But fx-0590 has only two keyframes, scale(1) → scale(2.2) and border-radius: 20px → 4px on a 120 px square, with no filter or opacity: measured ×1.6 at 260 px and ×2.2 at 520, where only 12 px of the square remain in the box — the largest is the final state, not sticky despite its name. fx-0563 adds the third keyframe, blur and opacity: the object is readable at one position and hazy on both sides — a resting point for the eye, a focus, not a zoom. fx-0590 to grow an object to the very end (a reveal), fx-0563 to have it pass through — appear, be read, fade — between two blocks of content.
Yes, by changing the timeline. scroll(root) takes the document's scroller, but the travel becomes the whole page and the sharp point lands at 50% of the page, rarely where the object is. The right answer is animation-timeline: view(): the timeline becomes the element's own crossing of the viewport — 0% when it enters from the bottom, 100% once out through the top, 50% at the center. Measured with that single line changed, in a 3,100 px page: ×0.7 / 6 px / .58 200 px after its entry, ×0.95 / 1 px / .93 50 px from the center, ×1.4 / 4 px / .72 on the way up, ×1.9 / 9 px / .37 before leaving. No more box or stage: remove .sdc-scroll-container and .sdc-zoom-content, keep overflow-x: hidden on the page for the 400 px of width at the end. Same support: Chrome 115, Safari 26, not Firefox.