Categories: CSSScriptWeb Design

Optimize Web Apps Based on Users’ Device, Network, and Battery Status – Obs.js

Obs.js is a lightweight JavaScript library that automatically adapts your web application’s content delivery based on users’ device capabilities, connection quality, and battery status.

It reads browser signals through the Navigator and Battery APIs to intelligently determine when to serve high-resolution media, disable animations, or reduce resource consumption.

Features:

    Sponsored
  • Automatic CSS class generation: Adds contextual classes to the HTML element based on connection and device status.
  • JavaScript API access: Provides detailed device and connection data through the window.obs object.
  • Battery-aware optimizations: Automatically detects critical and low battery states for resource conservation.
  • Connection quality assessment: Categorizes RTT and bandwidth into actionable tiers (low, medium, high).
  • Device capability detection: Evaluates RAM and CPU resources to determine hardware limitations.
  • Delivery mode recommendations: Provides three content delivery strategies (rich, cautious, lite).
  • Real-time monitoring: Optional change detection for single-page applications and long-lived pages.

Use Cases:

  • Responsive media delivery: Automatically serve low-resolution images and disable autoplay videos for users on slow connections or low battery devices.
  • Performance-sensitive applications: Disable expensive animations and transitions when battery levels drop below critical thresholds.
  • Progressive enhancement: Load rich interactive features only for devices with sufficient processing power and stable connections.
  • Data-conscious experiences: Respect users’ data saver preferences by reducing resource-heavy content automatically.

How to Use It

1. Obs.js must be included as an inline script in your document’s head section, positioned before any other scripts or stylesheets that depend on it. This timing ensures the library can apply CSS classes before your page content renders.

<script>
  /*! Obs.js 0.2.1 | (c) Harry Roberts, csswizardry.com | MIT */  ;(()=>{const e=document.currentScript;if((!e||e.src||e.type&&"module"===e.type.toLowerCase())&&!1===/^(localhost|127.0.0.1|::1)$/.test(location.hostname))return void console.warn("[Obs.js] Skipping: must be an inline, classic <script> in <head>.",e?e.src?"src="+e.src:"type="+e.type:"type=module");const t=document.documentElement,{connection:i}=navigator;window.obs=window.obs||{};const a=!0===(window.obs&&window.obs.config||{}).observeChanges,o=()=>{const e=window.obs||{},i="number"==typeof e.downlinkBucket?e.downlinkBucket:null;e.connectionCapability="low"===e.rttCategory&&null!=i&&i>=8?"strong":"high"===e.rttCategory||null!=i&&i<=5?"weak":"moderate";const a=!0===e.dataSaver||!0===e.batteryLow||!0===e.batteryCritical;e.conservationPreference=a?"conserve":"neutral";const o="weak"===e.connectionCapability||!0===e.dataSaver||!0===e.batteryCritical;e.deliveryMode="strong"!==e.connectionCapability||o||a?o?"lite":"cautious":"rich",e.canShowRichMedia="lite"!==e.deliveryMode,e.shouldAvoidRichMedia="lite"===e.deliveryMode,["strong","moderate","weak"].forEach(e=>{t.classList.remove(`has-connection-capability-${e}`)}),t.classList.add(`has-connection-capability-${e.connectionCapability}`),["conserve","neutral"].forEach(e=>{t.classList.remove(`has-conservation-preference-${e}`)}),t.classList.add(`has-conservation-preference-${e.conservationPreference}`),["rich","cautious","lite"].forEach(e=>{t.classList.remove(`has-delivery-mode-${e}`)}),t.classList.add(`has-delivery-mode-${e.deliveryMode}`)},n=()=>{if(!i)return;const{saveData:e,rtt:a,downlink:n}=i;window.obs.dataSaver=!!e,t.classList.toggle("has-data-saver",!!e);const s=(e=>Number.isFinite(e)?25*Math.ceil(e/25):null)(a);null!=s&&(window.obs.rttBucket=s);const r=(e=>Number.isFinite(e)?e<75?"low":e<=275?"medium":"high":null)(a);r&&(window.obs.rttCategory=r,["low","medium","high"].forEach(e=>t.classList.remove(`has-latency-${e}`)),t.classList.add(`has-latency-${r}`));const c=(l=n,Number.isFinite(l)?Math.ceil(l):null);var l;if(null!=c){window.obs.downlinkBucket=c;const e=c<=5?"low":c>=8?"high":"medium";window.obs.downlinkCategory=e,["low","medium","high"].forEach(e=>t.classList.remove(`has-bandwidth-${e}`)),t.classList.add(`has-bandwidth-${e}`)}"downlinkMax"in i&&(window.obs.downlinkMax=i.downlinkMax),o()};n(),a&&i&&"function"==typeof i.addEventListener&&i.addEventListener("change",n);const s=e=>{if(!e)return;const{level:i,charging:a}=e,n=Number.isFinite(i)?i<=.05:null;window.obs.batteryCritical=n;const s=Number.isFinite(i)?i<=.2:null;window.obs.batteryLow=s,["critical","low"].forEach(e=>t.classList.remove(`has-battery-${e}`)),s&&t.classList.add("has-battery-low"),n&&t.classList.add("has-battery-critical");const r=!!a;window.obs.batteryCharging=r,t.classList.toggle("has-battery-charging",r),o()};if("getBattery"in navigator&&navigator.getBattery().then(e=>{s(e),a&&"function"==typeof e.addEventListener&&(e.addEventListener("levelchange",()=>s(e)),e.addEventListener("chargingchange",()=>s(e)))}).catch(()=>{}),"deviceMemory"in navigator){const e=Number(navigator.deviceMemory),i=Number.isFinite(e)?e:null;window.obs.ramBucket=i;const a=(r=i,Number.isFinite(r)?r<=1?"very-low":r<=2?"low":r<=4?"medium":"high":null);a&&(window.obs.ramCategory=a,["very-low","low","medium","high"].forEach(e=>t.classList.remove(`has-ram-${e}`)),t.classList.add(`has-ram-${a}`))}var r;if("hardwareConcurrency"in navigator){const e=Number(navigator.hardwareConcurrency),i=Number.isFinite(e)?e:null;window.obs.cpuBucket=i;const a=(e=>Number.isFinite(e)?e<=2?"low":e<=5?"medium":"high":null)(i);a&&(window.obs.cpuCategory=a,["low","medium","high"].forEach(e=>t.classList.remove(`has-cpu-${e}`)),t.classList.add(`has-cpu-${a}`))}(()=>{const e=window.obs||{},i=e.ramCategory,a=e.cpuCategory;let o="moderate";"high"!==i&&"medium"!==i||"high"!==a?("very-low"===i||"low"===i||"low"===a)&&(o="weak"):o="strong",e.deviceCapability=o,["strong","moderate","weak"].forEach(e=>{t.classList.remove(`has-device-capability-${e}`)}),t.classList.add(`has-device-capability-${o}`)})()})();
  //# sourceURL=obs.inline.js
</script>

2. By default, Obs.js runs once on page load. If you’re building an SPA or have long-lived pages where the user’s context might change (e.g., they unplug their laptop), you can tell Obs.js to listen for changes.

<script>window.obs = { config: { observeChanges: true } }</script>
<!-- Then, the Obs.js inline script -->
<script>
  /*! Obs.js 0.2.1 ... */</script>

3. Obs.js automatically adds descriptive CSS classes to your HTML element. This can be useful for responsive media queries and progressive enhancement strategies.

Connection and Network

  • .has-data-saver: The user has enabled Data Saver mode in their browser.
  • .has-latency-low: The user has a low Round-Trip Time (RTT), typically under 75ms.
  • .has-latency-medium: The user has a medium RTT, between 75ms and 275ms.
  • .has-latency-high: The user has a high RTT, over 275ms.
  • .has-bandwidth-low: The user has low estimated bandwidth, around 5Mbps or less.
  • .has-bandwidth-medium: The user has medium estimated bandwidth, between 6-7Mbps.
  • .has-bandwidth-high: The user has high estimated bandwidth, around 8Mbps or more.

Battery

  • .has-battery-critical: The user’s battery is at 5% or less.
  • .has-battery-low: The user’s battery is at 20% or less.
  • .has-battery-charging: The user’s device is currently charging.

Device Hardware

  • .has-ram-very-low: The device has a very low amount of RAM (typically 1GB or less).
  • .has-ram-low: The device has a low amount of RAM (typically between 1GB and 2GB).
  • .has-ram-medium: The device has a medium amount of RAM (typically between 2GB and 4GB).
  • .has-ram-high: The device has a high amount of RAM (typically more than 4GB).
  • .has-cpu-low: The device has few logical CPU cores (2 or fewer).
  • .has-cpu-medium: The device has a moderate number of logical CPU cores (3 to 5).
  • .has-cpu-high: The device has many logical CPU cores (6 or more).

Derived Stances (Opinions)

  • .has-connection-capability-weak: The overall connection quality appears weak (e.g., high latency or low bandwidth).
  • .has-connection-capability-moderate: The overall connection quality is neither strong nor weak.
  • .has-connection-capability-strong: The overall connection quality appears strong (e.g., low latency and high bandwidth).
  • .has-device-capability-weak: The device hardware appears weak (e.g., low RAM or few CPU cores).
  • .has-device-capability-moderate: The device hardware is neither strong nor weak.
  • .has-device-capability-strong: The device hardware appears strong (e.g., high RAM and many CPU cores).
  • .has-conservation-preference-conserve: A signal for frugality is present (e.g., Data Saver is on or the battery is low).
  • .has-conservation-preference-neutral: No frugality signals are present.
  • .has-delivery-mode-lite: Instructs the site to be as frugal and lightweight as possible.
  • .has-delivery-mode-cautious: Instructs the site to be careful with resource usage.
  • .has-delivery-mode-rich: Signals that it’s okay to deliver a rich, full-media experience.

4. The window.obs object provides detailed information for more complex conditional logic. All possible JavaScript properties available on the window.obs object:

Connection and Network

  • dataSaver (boolean): true if the user has enabled Data Saver mode in their browser.
  • rttBucket (number): The user’s Round-Trip Time (RTT) in milliseconds, rounded up to the nearest 25ms.
  • rttCategory (‘low’ | ‘medium’ | ‘high’): A simplified category for the user’s RTT.
  • downlinkBucket (number): The user’s estimated network downlink speed in Mbps, rounded up to the nearest integer.
  • downlinkCategory (‘low’ | ‘medium’ | ‘high’): A simplified category for the user’s bandwidth.
  • downlinkMax (number): The maximum potential downlink speed for the user’s connection type, if available.

Battery

Sponsored
  • batteryCritical (boolean | null): true if the battery level is 5% or less. null if the Battery API is not supported.
  • batteryLow (boolean | null): true if the battery level is 20% or less. null if the Battery API is not supported.
  • batteryCharging (boolean | null): true if the device is currently charging. null if the Battery API is not supported.

Device Hardware

  • ramBucket (number): The device’s RAM in gigabytes (e.g., 0.5, 1, 2, 4, 8).
  • ramCategory (‘very-low’ | ‘low’ | ‘medium’ | ‘high’): A simplified category for the device’s RAM.
  • cpuBucket (number): The number of logical CPU cores on the device.
  • cpuCategory (‘low’ | ‘medium’ | ‘high’): A simplified category based on the number of CPU cores.

Derived Stances (Opinions)

  • connectionCapability (‘strong’ | ‘moderate’ | ‘weak’): An overall assessment of the network connection quality.
  • deviceCapability (‘strong’ | ‘moderate’ | ‘weak’): An overall assessment of the device’s hardware capabilities.
  • conservationPreference (‘conserve’ | ‘neutral’): An inference about whether the user prefers to save data or battery.
  • deliveryMode (‘rich’ | ‘cautious’ | ‘lite’): A recommendation on how “heavy” the content you serve should be.
  • canShowRichMedia (boolean): A convenient shorthand for deliveryMode !== 'lite'.
  • shouldAvoidRichMedia (boolean): A convenient shorthand for deliveryMode === 'lite'.
{
  config: { observeChanges: false },
  dataSaver: false,
  rttBucket: 50,
  rttCategory: "low",
  downlinkBucket: 10,
  connectionCapability: "strong",
  deliveryMode: "rich",
  canShowRichMedia: true,
  shouldAvoidRichMedia: false,
  batteryCritical: false,
  batteryLow: false,
  batteryCharging: true,
  ramBucket: 8,
  ramCategory: "high",
  cpuBucket: 8,
  cpuCategory: "high",
  deviceCapability: "strong",
  // ...
}

5. Note that Most APIs used by Obs.js are only available in Chromium-based browsers. Safari and Firefox users will not trigger the automatic classifications, so you’ll need to decide on default behavior:

// Option 1: Default to rich content for unsupported browsers
if (window.obs?.shouldAvoidRichMedia === true) {
  loadLiteVersion();
} else {
  loadRichVersion(); // Includes Safari users
}

// Option 2: Default to lite content for unsupported browsers  
if (window.obs?.canShowRichMedia === true) {
  loadRichVersion(); // Only confirmed fast browsers
} else {
  loadLiteVersion(); // Includes Safari users
}

FAQs

Q: Does Obs.js work on iOS devices and Safari browsers?
A: Most APIs that Obs.js relies on are not available in Safari or iOS browsers. The library will still load without errors, but CSS classes and JavaScript properties won’t reflect actual device conditions. You’ll need to implement fallback logic that assumes either rich or lite delivery for these browsers.

Q: How accurate are the connection speed measurements?
A: The Network Information API provides estimates rather than precise measurements. Connection speeds can vary significantly based on network congestion, server response times, and other factors. Obs.js buckets these values into broad categories specifically because precise measurements aren’t reliable for real-world decision making.

Q: Can I customize the battery percentage thresholds?
A: The current version uses fixed thresholds of 5% for critical and 20% for low battery status. These values aren’t configurable, but you can implement custom logic using the raw battery.level value available through the Battery Status API directly.

Q: Can I use Obs.js data for analytics or user experience tracking?
A: Yes, the window.obs object contains detailed device and connection data that can be sent to analytics endpoints. This information provides valuable insights into your user base’s technical capabilities and can inform performance optimization strategies.

Q: Why must the script be inlined in the head?
A: To prevent a Flash of Unstyled Content (FOUC). If the script were loaded externally or placed at the end of the <body>, the browser might start rendering the page with default styles before the script has a chance to run and add its contextual classes.

The post Optimize Web Apps Based on Users’ Device, Network, and Battery Status – Obs.js appeared first on CSS Script.

rssfeeds-admin

Share
Published by
rssfeeds-admin

Recent Posts

Hackers Use Fake CleanMyMac Site to Deploy SHub Stealer and Hijack Crypto Wallets

A convincing fake website posing as the popular Mac utility CleanMyMac is actively pushing dangerous…

49 minutes ago

BoryptGrab Stealer Spreads via Fake GitHub Repositories, Stealing Browser and Crypto Wallet Data

A new data-stealing malware called BoryptGrab has been quietly spreading across Windows systems through a…

49 minutes ago

Apple smart home display rumors now point to a fall launch with iOS 27

The rumored "HomePod with a screen" we've heard so much about was reportedly lined up…

2 hours ago

The government shutdown is hitting airports — but not ICE

Department of Homeland Security. | Image: The Verge Chaos reigned at airports across the country…

2 hours ago

New water treatment facility the “final puzzle piece” for clean water in Freeport

City and project leaders recently broke ground on a new well and water treatment facility…

2 hours ago

Save $1,000 Off the Massively Powerful Acer Predator Helios 18″ RTX 5090 Gaming Laptop

If you're in the market for the biggest and baddest mobile desktop replacement at a…

2 hours ago

This website uses cookies.