惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News - Newest:
Hacker News - Newest: "LLM"
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs
H
Heimdal Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
N
News and Events Feed by Topic
H
Hacker News: Front Page
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
SecWiki News
SecWiki News
N
News | PayPal Newsroom
T
Tor Project blog
W
WeLiveSecurity
A
Arctic Wolf
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
月光博客
月光博客
AWS News Blog
AWS News Blog
D
Docker
C
CERT Recently Published Vulnerability Notes
MyScale Blog
MyScale Blog
Google Online Security Blog
Google Online Security Blog
大猫的无限游戏
大猫的无限游戏
T
The Blog of Author Tim Ferriss
I
InfoQ
人人都是产品经理
人人都是产品经理
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
Hacker News: Ask HN
Hacker News: Ask HN
Blog — PlanetScale
Blog — PlanetScale
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
罗磊的独立博客
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
The Exploit Database - CXSecurity.com
D
DataBreaches.Net
S
Security Affairs
WordPress大学
WordPress大学
T
Threatpost
Microsoft Security Blog
Microsoft Security Blog
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
S
SegmentFault 最新的问题
B
Blog RSS Feed
Project Zero
Project Zero
P
Proofpoint News Feed

CSS Wizardry

Front-End’s Missing Metric: The TBT Window Better Browser Caching with No-Vary-Search font-family Doesn’t Fall Back the Way You Think What Is CSS Containment and How Can I Use It? When All You Can Do Is All or Nothing, Do Nothing Obs.js: Context-Aware Web Performance for Everyone Low- and Mid-Tier Mobile for the Real World (2025) The Fastest Site in the Tour de France Making Sense of the Performance Extensibility API Why Do We Have a Cache-Control Request Header? HTML Is Not a Programming Language… Build for the Web, Build on the Web, Build with the Web Licensing Code on CSS Wizardry A Layered Approach to Speculation Rules Designing (and Evolving) a New Web Performance Score Core Web Vitals Colours The Ultimate Contract Templates for Tech Consultants: Protect Your Business and Get Paid Optimising for High Latency Environments Cache Grab: How Much Are You Leaving on the Table? blocking=render: Why would you do that?! Correctly Configure (Pre) Connections The Three Cs: 🤝 Concatenate, 🗜️ Compress, 🗳️ Cache What Is the Maximum max-age? How to Clear Cache and Cookies on a Customer’s Device The Ultimate Low-Quality Image Placeholder Technique Core Web Vitals for Search Engine Optimisation: What Do We Need to Know? The HTTP/1-liness of HTTP/2 In Defence of DOM­Content­Loaded Site-Speed Topography Remapped Why Not document.write()? Speeding Up Async Snippets Critical CSS? Not So Fast! Measure What You Impact, Not What You Influence Optimising Largest Contentful Paint Measuring Web Performance in Mobile Safari Site-Speed Topography Speed Up Google Fonts Real-World Effectiveness of Brotli Performance Budgets, Pragmatically Lazy Pre-Browsing with Prefetch Making Cloud.typography Fast(er) Time to First Byte: What It Is and How to Improve It Self-Host Your Static Assets Tips for Technical Interviews Cache-Control for Civilians Bandwidth or Latency: When to Optimise for Which ITCSS × Skillshare What If? CSS and Network Performance The Three Types of Performance Testing Getting to Know a Legacy Codebase Image Inconsistencies: How and When Browsers Download Images Identifying, Auditing, and Discussing Third Parties My Digital Music Setup Measuring the Hard-to-Measure Finding Dead CSS The Fallacies of Distributed Computing (Applied to Front-End Performance) Ten Years Old Relative Requirements Airplanes and Ashtrays Performance and Resilience: Stress-Testing Third Parties Refactoring Tunnels Little Things I Like to Do with Git Writing Tidy Code Configuring Git and Vim Base64 Encoding & Performance, Part 2: Gathering Data Base64 Encoding & Performance, Part 1: What’s Up with Base64? Code Smells in CSS Revisited Typography for Developers Moving CSS Wizardry onto HTTPS and HTTP/2 Ack for CSS Developers A New Year, a New Focus Preparing Vim for Apple’s Touch Bar Choosing the Correct Average CSS Shorthand Syntax Considered an Anti-Pattern CSS Wizardry Newsletter Nesting Your BEM? Improving Perceived Performance with Multiple Background Images Continue Normalising Your CSS Pure CSS Content Filter Pragmatic, Practical, and Progressive Theming with Custom Properties Refactoring CSS: The Three I’s Speaker’s Checklist: Before and After Your Talk Improving Your CSS with Parker The Importance of !important: Forcing Immutability in CSS Mixins Better for Performance Managing Typography on Large Apps White October Events Workshop Partnership BEMIT: Taking the BEM Naming Convention a Step Further Travelling Like You Want to, When You Have To Contextual Styling: UI Components, Nesting, and Implementation Detail Subtleties with Self-Chained Classes Cyclomatic Complexity: Logic in CSS Immutable CSS Can CSS Be Too Modular? More Transparent UI Code with Namespaces When to use @extend; when to use a mixin The Specificity Graph CSS Wizardry Ltd.: Year 1 in review CSS Guidelines 2.0.0
Meet Your Users Where They Are with Obs.js
Harry Roberts · 2026-05-13 · via CSS Wizardry

Written by on CSS Wizardry.

Table of Contents

Independent writing is brought to you via my wonderful Supporters.

  1. A Real Example
  2. Your Site Is Only Half the Story
  3. Statuses and Stances
  4. Considerate, Not Clever
  5. Understanding Your Audience
  6. Browser Support and Limitations
  7. A Very Practical Idea

One thing I encourage all of my clients to remember is that web performance happens somewhere between you and your user. The exact same page on the exact same infrastructure can feel vastly different to two different people, and knowing where the two meet is key to designing truly fast experiences.

Over the last few years, I’ve written a fair bit about high latency environments and low- and mid-tier mobile, and one of the recurring themes in both is that site-speed is only partly a property of the site itself. A large part of it is a property of the conditions under which that site is being consumed.

Sometimes, web performance really is a them problem.

It’s not our fault that someone is on a struggling connection, a weaker device, or a battery that is nearly dead, but it is still our responsibility to design around those scenarios where we can. To help, I built Obs.js, a tiny library which tells us a large amount about our users’ context.

I released Obs.js in summer 2025, and while it’s been incredibly useful and insightful (instrumental, even) on several client projects, I haven’t really talked about it much since then. Today, I will.

If you want the more direct technical walkthrough, I’ve written that up in Obs.js: Context-Aware Web Performance for Everyone. This piece is the companion to that one. This is less about the API itself and more about why I think having this sort of signal available to us matters.

A Real Example

Obs.js reads browser signals about our users’ connection, device capability, battery status, and more. We can then use this information to adapt and tailor our front-end code to suit their conditions. And while I can’t share client work here, I can share a small but very real example of my own.

On my homepage, I use Obs.js to alter the masthead imagery depending on the browser’s inferred delivery mode. In the faster case, I use the full, high-res image stack:

.page-head--masthead {
  background-image:
    url(/img/css/masthead-small.jpg),
    url(/img/css/masthead-small-lqip.jpg),
    var(--base64);
}

And if Obs.js decides the visitor is better served by lite mode, I drop down to the LQIP-only variant:

.has-delivery-mode-lite .page-head--masthead {
  background-image:
    url(/img/css/masthead-small-lqip.jpg),
    var(--base64);
}

There’s nothing massively sophisticated going on here, and that’s exactly why I like it. I’m not trying to build some elaborate adaptive-delivery system; I’m just making a small adjustment in response to a signal from the browser.

The page is still the same page, on the same infrastructure, served by the same code. Only one visitor gets the richer masthead stack, and another gets the LQIP-only version. The difference lies not in my application but in the context in which it is being viewed.

What I find reassuring is that this is not creating some sort of second-rate experience for visitors in lite mode, and, happily, the numbers prove it: SpeedCurve shows that across 3,777 page views in lite mode and 4,965 in rich mode, Largest Contentful Paint is only 80ms apart! Virtually identical experiences.

Comparison of Lite and Rich delivery mode cohorts showing comparable outcomes: Lite records 0.9s Largest Contentful Paint, Rich records 0.98s, and both cohorts record 16ms Interaction to Next Paint and zero Cumulative Layout Shift.
Across similarly sized lite and rich cohorts, LCP remains within 80ms, while INP and CLS are identical.

I make similar adaptations with my nav—users with low- or critically-low battery will not be shown any superfluous animations, and instead just have a much simpler open/closed experience. Every little helps, and I can adapt to fit.

Your Site Is Only Half the Story

What I like about Obs.js is that it is very honest about the problem it’s addressing. It is not pretending to make weak devices stronger or poor networks faster; it cannot reduce the distance between somebody and your servers, prolong battery life, or upgrade their handset from within the confines of a browser tab. But what it can do is give us a slightly clearer picture of the conditions under which our work is being experienced.

It reads a handful of browser signals—latency, bandwidth, Data Saver, battery, CPU, memory—and exposes them as classes on the <html> element and as a small window.obs object in JavaScript. This simple functionality opens up a whole world of potential, and it’s up to us as developers to exploit it.

The value is not that it makes decisions for us—it doesn’t—the value is that it gives us better information with which to make our own decisions. Perhaps that means avoiding rich media; perhaps it means serving lower resolution imagery. Perhaps it means toning down motion; perhaps it means holding back a web font. The exact response is still up to us, as it should be, but we get to replace a little guesswork with a little evidence.

Remember, web performance is partly about you, and partly about them. Prior to Obs.js, there wasn’t much we could do to know about them until it was too late.

Statuses and Stances

The library makes the distinction between Statuses and Stances. A Status is factual: the user has Data Saver enabled, the observed latency is high, the battery is low, the device is weak. A Stance is the opinion we derive from that: the connection looks weak, the user may prefer to conserve resources, the safest delivery mode is lite, rich media is probably not a great idea right now.

That distinction matters because it keeps raw signals separate from opinions and decisions. Sometimes I want the low-level information because I already know how I want to react to it. At other times, I am perfectly happy for the library to have an opinion and hand me something a little more usable, such as deliveryMode, canShowRichMedia, or shouldAvoidRichMedia.

This is a nice level of abstraction to have because it leaves room for both approaches. If you want to be opinionated yourself, you can be. If you would rather start from a decent default and get on with it, you can do that, too.

Considerate, Not Clever

I do not think this sort of work is interesting because it makes sites feel clever. I think it is interesting because it gives us a better shot at making them feel more considerate.

It is very easy to ship the heaviest possible version of an experience by default simply because our own machines can tolerate it. It is very easy to look at a feature in isolation and decide that, yes, obviously the autoplaying video or the heavy animation or the higher-resolution image would be better if circumstances are ideal, but what if we had a better idea of whether circumstances actually are ideal?

That’s the bit I care about. It shifts us away from asking can we ship this? and more toward asking should we ship this to this visitor, under these conditions, right now? The answer may often still be yes, but at least we are asking the question.

Understanding Your Audience

One of the less flashy but still very practical side effects of Obs.js is that it doubles as a segmentation layer for your analytics. If your tooling supports custom dimensions, you can beacon some of the Obs.js signals off alongside the rest of your performance data and stop treating your audience as one opaque average.

At that point, you can start asking much more useful questions. How much worse is INP for weaker devices? What proportion of your traffic is on high-latency connections? How often are you seeing Data Saver in the wild? Are visitors in lite delivery mode behaving differently? Even if you never adapt a single byte of the UI, that is still useful knowledge to have because it gives you a richer picture of who your users actually are.

For example, while INP is a developer’s problem to solve, it is highly influenced by the power of the device being used. Same code, three different scores:

Interaction to Next Paint on csswizardry.com broken down by CPU class: low records 72ms, medium records 48ms, and high records 16ms.
Same site and same code, but INP ranges from 16ms on high-CPU devices to 72ms on low-CPU ones.

Sometimes, it’s a them-thing.

This has helped me immensely in recent projects where we had unknown unknowns that left us completely in the dark. Knowing if it’s an us-problem or a them-problem can completely change the course of an engagement, and it has! One particular project showed us that conversion rates were higher among users with low or critical battery. This hints at a potential sense of urgency, so perhaps we adapt the checkout flow to remove as much unnecessary friction as possible, delivering the absolute most bare-minimum checkout experience we can.

Browser Support and Limitations

Most of the underlying APIs are Chromium-heavy. Safari, in particular, is not going to tell you very much, and I do not really see that as a flaw in Obs.js so much as a reminder of the limits of what the platform currently exposes. Obs.js is progressive enhancement at its finest, so treat it as an extra vector rather than a baseline.

The library does not pretend to know what it cannot know. It gives you the signals it can get, and it leaves the fallback policy up to you. Maybe your default is the richer experience and the browser helps you dial things down where needed, or maybe your default is the lighter experience and the browser helps you selectively dial things up. Either approach is reasonable, the important thing is that the decision becomes explicit rather than accidental.

A Very Practical Idea

There are a lot of good ideas in web performance that remain just slightly too academic, or slightly too fiddly, to make it into real work. Obs.js tries to avoid that problem.

It is tiny, it gives you a bit more context than you would otherwise have, and it asks very little in return. Paste it into the <head>, look at the classes it adds, and start making small decisions from there: serve the smaller image, skip the autoplay, avoid the custom font, tone down the motion, defer the nice-to-have. None of those are especially dramatic interventions, but they are sensible adjustments made with a little more information than we usually have.

That is why I like it so much. It gives us a practical and honest way to acknowledge a truth that has always been there: performance does not happen in a vacuum, and the front end does not arrive at the user unchanged by the journey it took. If you have never looked at Obs.js before, go and play with the demo, check out the repo, and inspect obs.js itself. I think the underlying idea is a useful one whether you use my little library or not.

If you’re already using Obs.js, open a Pull Request to submit your site to the showcase.