You can’t reliably detect “low-end device” on the web in a way that’s both accurate and future-proof. What you can do is gate blur by (a) explicit user prefs, (b) known capability hints where available, and (c) a runtime performance probe—and always provide an opt-out.
Here are the approaches that actually work in 2025, in descending order of sanity.
This is the closest thing to “don’t do frosted glass” the platform has. It’s now a real media query and increasingly supported. citeturn0search2turn0search6turn0search10
background: color-mix(in srgb, Canvas 70%, transparent);
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
@media (prefers-reduced-transparency: reduce) {
-webkit-backdrop-filter: none;
background: Canvas; /* or a near-opaque token */
Also consider prefers-reduced-motion: reduce as a secondary hint (not equivalent, but often correlated with “avoid heavy effects”).
This is not available on Safari (and not Baseline). citeturn0search0turn0search16
So it won’t help for iPhones/iOS Safari, but it can help on low-end Android/Chromebooks.
The Network Information API is “limited availability” and WebKit has an open feature request around saveData. citeturn0search1turn0search9turn0search17
This one is supported in Safari (Apple documents it), so you can use it as a weak heuristic. citeturn0search11turn0search7turn0search3
Example heuristic (don’t treat as truth; treat as “maybe be conservative”):
function shouldDisableBlurByHints() {
// Respect explicit user pref first (handled in CSS above).
// Now apply coarse hints where present.
const mem = navigator.deviceMemory; // undefined on Safari
if (typeof mem === "number" && mem <= 2) return true;
// Weak signal; iPhones are usually >=6 cores these days, but don't assume.
const cores = navigator.hardwareConcurrency;
if (typeof cores === "number" && cores <= 4) return true;
// Network hints (often undefined on Safari)
const conn = navigator.connection;
if (conn?.saveData) return true;
If your concern is “does blur cause scroll jank on this device on this page?”, measure it.
A simple, practical strategy:
- Enable blur by default.
- On first interaction (or after a short sample), run a requestAnimationFrame FPS probe while the user scrolls, and if it’s consistently bad, disable blur for that session (or remember the choice).
Minimal version (session-only):
const root = document.documentElement;
function setBlurEnabled(enabled) {
root.classList.toggle("no-blur", !enabled);
if (shouldDisableBlurByHints()) setBlurEnabled(false);
// Optional: runtime probe
let samples = 0, bad = 0;
let last = performance.now();
// ~60fps => 16.7ms; treat sustained >24ms as trouble (tune to your UX)
requestAnimationFrame(tick);
// If >20% of frames were "slow", disable blur
if (bad / samples > 0.2) setBlurEnabled(false);
requestAnimationFrame(tick);
And the CSS toggle:
-webkit-backdrop-filter: none;
This avoids guessing “low-end” and instead answers “is this effect harming this user right now?”
- UA sniffing for “old iPhone models” (fragile, and you’ll still be wrong on thermal/battery states).
- Assuming “modern iPhones are always fine” (they often are, until the page is heavy or you pick huge blur radii like
100px).
If you’re serious about gating, the simplest robust policy is:
- Default blur to ~12–20px, not 100px.
- Disable blur on
prefers-reduced-transparency: reduce. citeturn0search2turn0search6
- Optional: run the short FPS probe and disable if it’s janky.
If you paste your navbar CSS and whether it’s position: sticky or fixed and its height, I can suggest a “looks the same, costs less” set of values and structure.