You Might Also Like
Scroll Velocity Motion Blur — Free Real Velocity-Based CSS Blur Effect
Scroll Velocity Motion Blur · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Scroll Velocity Motion Blur — Blur That Tracks Real Scroll Speed

This isn't a fixed "blur while scrolling, sharp when still" toggle — the blur amount is a genuine, continuously computed function of how fast the page is actually moving. Scroll slowly and the content stays crisp; flick the page hard and it visibly smears with filter: blur(), then eases back to zero as your scroll speed drops. Built entirely with vanilla JavaScript and requestAnimationFrame, no animation library required.
Measuring real velocity, not just "is scrolling"
Every scroll event records the current window.scrollY, compares it against the value from the previous event, and divides by the elapsed time — normalized to a 16.67ms (60fps) frame so the result reads as "pixels moved per frame" regardless of how often the browser actually fires scroll events. That raw instantaneous velocity is smoothed with a simple exponential-moving-average step (velocity += (instVelocity - velocity) * 0.35) so a single noisy event doesn't spike the blur.
A second smoothing pass for the visual blur
The blur amount applied to the content doesn't jump straight to the velocity-derived target either — a requestAnimationFrame loop eases currentBlur toward targetBlur every frame with its own smoothing factor. Two smoothing stages (velocity, then blur) is what gives the effect a soft ramp-up and a soft settle rather than a jittery on/off flicker, while the underlying number driving it all is still a real speed measurement.
Decay when scrolling stops
Because scroll events stop firing entirely once the page is still, the rAF loop separately checks how long it's been since the last event and decays the velocity toward zero if none has arrived in the last couple of frames. Without this, a scroll that stops abruptly (rather than decelerating smoothly, as happens with certain trackpads or a scrollbar drag) would leave the blur stuck at its last value with nothing to bring it back down.
Live velocity readout
A small fixed meter in the corner displays the raw pixels-per-frame velocity number in real time, which makes the relationship between scroll speed and blur amount directly observable rather than something you have to take on faith — useful for tuning MAX_BLUR and the smoothing constants to taste.
Customizing it
Raise MAX_BLUR for a more dramatic smear, tighten BLUR_SMOOTHING for a snappier response, or apply the blur to individual cards instead of the whole list for a more localized effect. Pair it with scroll skew velocity for a combined blur-and-skew treatment, or contrast it with a scroll reveal grid for a calmer entrance.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's JS into an AI coding assistant like Claude and ask it to walk through the two-stage smoothing pipeline — why the raw per-event velocity is smoothed once with an exponential moving average before becoming a "target," and why the actual applied CSS blur value is then smoothed a second time toward that target inside the requestAnimationFrame loop, rather than either value being applied directly and instantly. It's also useful for tuning: ask it whether MAX_BLUR of 14px and a BLUR_SMOOTHING of 0.18 feel right for a hero section versus a dense card list, or to add a floor so a very slow, deliberate scroll never triggers even a hint of blur. It can also help extend the idea — applying directional blur (only horizontal or only vertical) based on scroll direction, or scaling blur per-element based on each element's distance from viewport center.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll velocity motion blur" effect in plain HTML, CSS, and vanilla JavaScript (no libraries).
Requirements:
- A scrollable content area (a list of cards or similar) that gets a CSS filter: blur() applied, with the blur amount computed from real, measured scroll velocity — not a fixed value toggled on/off based on whether scrolling is happening.
- On each scroll event, compute an instantaneous velocity as the pixel distance scrolled since the previous scroll event divided by the elapsed time in milliseconds, normalized to a 60fps frame (i.e. treat 16.67ms as one frame) so the result reads as "pixels moved per frame."
- Smooth that raw per-event velocity with an exponential moving average (e.g. velocity += (instantVelocity - velocity) * 0.35) so a single noisy event doesn't spike the result.
- Run a separate requestAnimationFrame loop that: (a) if no scroll event has fired in roughly the last 2 frames, decays the velocity toward zero so the blur clears even if scrolling stops abruptly with no final low-velocity event; (b) computes a target blur amount in pixels proportional to the smoothed velocity, clamped to a maximum (e.g. 14px); (c) eases the actually-applied blur value toward that target with its own smoothing factor; (d) writes the result to the element's filter style only when it changes meaningfully.
- Add a small fixed-position readout on screen showing the live numeric velocity value so the relationship between scroll speed and blur amount is directly observable.
- Confirm behavior by testing: scrolling slowly should produce little to no blur, flicking the page fast should produce a clearly visible blur that scales with how hard you flick, and stopping (even abruptly) should bring the blur back to exactly zero within a few frames — not leave it stuck.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSAn intro, a card list, a meter, and an outro render.
- 2Scroll slowlyThe meter stays low and the cards remain sharp.
- 3Flick-scroll fastThe meter spikes and the cards visibly blur.
- 4Stop suddenlyBlur eases back to zero even without a final scroll event.
- 5Watch the meterIt shows the live pixels-per-frame value driving the blur.
- 6Tune the responseAdjust MAX_BLUR and the smoothing constants to taste.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Yes. Each scroll event computes the pixel distance moved since the previous event divided by elapsed time, normalized to a 60fps frame, producing a real "pixels per frame" velocity number. That number (after smoothing) directly sets the blur target in pixels, so a fast flick produces measurably more blur than a slow scroll — verifiable by watching the live meter track both.
Raw scroll events are noisy and irregularly timed, so smoothing the velocity first with an exponential moving average avoids a single jumpy event spiking the number. Then easing the applied blur toward that smoothed velocity target, rather than snapping to it every frame, is what gives the blur a soft ramp instead of a flicker — two lightweight smoothing passes instead of one heavier one.
Scroll events simply stop firing once the page is still, so a separate requestAnimationFrame loop tracks how long it has been since the last scroll event and decays the velocity toward zero if none has arrived within about two frames. Without this check, a scroll that stops instantly (rather than decelerating) would leave the last measured blur amount stuck on screen indefinitely.
The scroll listener is passive and does only cheap arithmetic — no DOM reads that trigger layout. The actual style write (filter) happens once per animation frame in the rAF loop rather than once per scroll event, and filter/blur is GPU-composited, so it stays smooth even during a fast, sustained scroll.
Attach the scroll listener and start the rAF loop in a mount effect, storing velocity and currentBlur in refs rather than plain variables so they persist across renders without triggering re-renders themselves; apply the computed blur directly via a ref's style property. Return a cleanup that removes the scroll listener and cancels the animation frame.