Skip to content

Commit 73d2a9f

Browse files
committed
Initial docs
1 parent c5292a2 commit 73d2a9f

3 files changed

Lines changed: 186 additions & 27 deletions

File tree

LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ MIT License
33
Copyright (c) 2025 Harry Roberts
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
6-
of this software and associated documentation files (the "Software"), to deal
6+
of this software and associated documentation files (the Software), to deal
77
in the Software without restriction, including without limitation the rights
88
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99
copies of the Software, and to permit persons to whom the Software is
@@ -12,7 +12,7 @@ furnished to do so, subject to the following conditions:
1212
The above copyright notice and this permission notice shall be included in all
1313
copies or substantial portions of the Software.
1414

15-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LICENSE.md

Lines changed: 0 additions & 21 deletions
This file was deleted.

README.md

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,185 @@
1-
# Obs
1+
# Obs.js
22

3-
> Nursing observations (obs) are routine checks to monitor your body while you
4-
> recover during your admission.
5-
> [NHS East Suffolk and North Essex](https://www.esneft.nhs.uk/leaflet/our-quick-guide-to-nursing-observations/)
3+
Obs.js uses the Navigator and Battery APIs to get contextual information about
4+
your users’ connection strength and battery status.
5+
6+
You can use this data to adapt your site/app to their environment, or beacon the
7+
data off to an analytics endpoint.
8+
9+
At its simplest, Obs.js will add a suite of classes to your `<html>` element,
10+
e.g.:
11+
12+
```html
13+
<html class="has-latency-low
14+
has-bandwidth-high
15+
has-battery-charging
16+
has-connection-capability-strong
17+
has-conservation-preference-neutral
18+
has-delivery-mode-rich">
19+
```
20+
21+
This means you could do something like this:
22+
23+
```css
24+
/**
25+
* Disable all animations and transitions if a user’s battery is below 5%.
26+
*/
27+
.has-battery-critical,
28+
.has-battery-critical * {
29+
animation: none;
30+
transition: none;
31+
}
32+
```
33+
34+
Or this:
35+
36+
```css
37+
body {
38+
background-image: url('hi-res.jpg');
39+
}
40+
41+
/**
42+
* Show low-resolution images if the user can’t take rich media right now.
43+
*/
44+
.has-delivery-mode-lite body {
45+
background-image: url('lo-res.jpg');
46+
}
47+
```
48+
49+
It also exposes this, and more, information via the `window.obs` object:
50+
51+
```js
52+
{
53+
"config": {
54+
"observeChanges": false
55+
},
56+
"dataSaver": false,
57+
"rttBucket": 50,
58+
"rttCategory": "low",
59+
"downlinkBucket": 10,
60+
"connectionCapability": "strong",
61+
"conservationPreference": "neutral",
62+
"deliveryMode": "rich",
63+
"canShowRichMedia": true,
64+
"shouldAvoidRichMedia": false,
65+
"batteryCritical": false,
66+
"batteryLow": false,
67+
"batteryCharging": true
68+
}
69+
```
70+
71+
This means you could do something like this:
72+
73+
```html
74+
<!--
75+
- Fetch low-resolution poster/placeholder image regardless.
76+
-->
77+
<link rel=preload as=image href=poster.jpg>
78+
79+
<div class=media-placeholder style="background-image: url(poster.jpg);">
80+
81+
<script>
82+
83+
const mediaPlaceholder = document.querySelector('.media-placeholder');
84+
85+
if (window.obs && window.obs.canShowRichMedia) {
86+
// If we can show rich media, load the video with the poster image in place.
87+
const v = document.createElement('video');
88+
v.src = 'video.mp4';
89+
v.poster = 'poster.jpg';
90+
v.autoplay = true;
91+
v.muted = true;
92+
v.playsInline = true;
93+
v.setAttribute('controls', '');
94+
mediaPlaceholder.replaceChildren(v);
95+
} else {
96+
// If not, just show the poster image as an image element.
97+
const img = new Image();
98+
img.src = 'poster.jpg';
99+
img.alt = '';
100+
mediaPlaceholder.replaceChildren(img);
101+
}
102+
103+
</script>
104+
105+
</div>
106+
```
107+
108+
## Installation
109+
110+
Obs.js **MUST** be placed in an inline `<script>` tag in the `<head>` of your
111+
document, before any other scripts, stylesheets, or HTML that may depend on it.
112+
113+
### Listen for Changes
114+
115+
If you have long-lived pages or a single-page app, you can instruct Obs.js to
116+
listen for changes to the connection and battery status by setting the following
117+
config:
118+
119+
```html
120+
<script>window.obs = { config: { observeChanges: true } }</script>
121+
122+
<script>
123+
// Obs.js
124+
</script>
125+
```
126+
127+
The default is `false`, which means Obs.js will only run once on each page load.
128+
This is sufficient for most non-SPA sites.
129+
130+
## Statuses and Stances
131+
132+
The information provided by Obs.js is split into two categories: **Statuses**
133+
and **Stances**.
134+
135+
* A **Status** is a factual piece of information, such as whether the user has
136+
enabled Data Saver, or whether their battery is charging, or if they are on
137+
a high latency connection.
138+
* A **Stance** is an opinion derived from Statuses. For example, if the user has
139+
enabled Data Saver or their battery is low, we might say they have
140+
a **conservation preference** of `conserve`, meaning they might prefer to save
141+
resources.
142+
143+
You can use either Statuses or Stances in your CSS or JavaScript.
144+
145+
## Available CSS Classes and JS Properties
146+
147+
Obs.js exposes the following classes under the following conditions:
148+
149+
| Class | Meaning | Computed/derived from |
150+
| --------------------------------------- | ------------------------ | --------------------------------------------------------------- |
151+
| `.has-data-saver` | User enabled Data Saver | `navigator.connection.saveData === true` |
152+
| `.has-battery-critical` | Battery ≤ 5% | `battery.level ≤ 0.05` |
153+
| `.has-battery-low` | 5% < battery ≤ 20% | `0.05 < battery.level ≤ 0.2` (mutually exclusive with critical) |
154+
| `.has-battery-charging` | On charge | `battery.charging === true` |
155+
| `.has-latency-low` | Low RTT | `rtt < 75ms` |
156+
| `.has-latency-medium` | Medium RTT | `75–275ms` |
157+
| `.has-latency-high` | High RTT | `> 275ms` |
158+
| `.has-bandwidth-low` | Low estimated bandwidth | `downlinkBucket ≤ 5` (1 Mbps buckets via `Math.ceil`) |
159+
| `.has-bandwidth-high` | High estimated bandwidth | `downlinkBucket ≥ 8` (6–7 is a dead zone → no class) |
160+
| `.has-connection-capability-strong` | Transport looks strong | `latency = low` **and** `bandwidth = high` |
161+
| `.has-connection-capability-moderate` | Transport middling | Anything not strong/weak |
162+
| `.has-connection-capability-weak` | Transport looks weak | `latency = high` **or** `bandwidth = low` |
163+
| `.has-conservation-preference-conserve` | Frugality signal present | `dataSaver === true` **or** `batteryLow === true` |
164+
| `.has-conservation-preference-neutral` | No frugality signal | Battery isn’t low and Data Saver is not enabled |
165+
166+
These classes are automatically added to the `<html>` element.
167+
168+
Obs.js also stores the following properties on the `window.obs` object:
169+
170+
| Property | Type | Meaning | Computed/derived from | Notes |
171+
| ------------------------ | ---------------------------------- | --------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
172+
| `config.observeChanges` | boolean | Whether Obs.js attaches change listeners | **Default `false`**; set by you _before_ Obs.js runs | Opt-in for SPAs or long-lived pages |
173+
| `dataSaver` | boolean | User enabled Data Saver | `navigator.connection.saveData` ||
174+
| `rttBucket` | number (ms) | RTT bucketed to **ceil** 25 ms (e.g. 101→125) | `navigator.connection.rtt` | Undefined if Connection API missing |
175+
| `rttCategory` | `'low' \| 'medium' \| 'high'` | CrUX tri-bin: `<75`, `75–275`, `>275` | Derived from `rtt` | Drives latency classes |
176+
| `downlinkBucket` | number (Mbps) | Downlink bucketed to **ceil** 1 Mbps | `navigator.connection.downlink` | Low/High thresholds: `≤5` / `≥8` |
177+
| `downlinkMax` | number (Mbps) | Max estimated downlink (if exposed) | `navigator.connection.downlinkMax` | Not used for Stances; informational only |
178+
| `connectionCapability` | `'strong' \| 'moderate' \| 'weak'` | Transport assessment | Derived from `rttCategory` and `downlinkBucket` | Strong = low RTT **and** high BW; Weak = high RTT **or** low BW |
179+
| `conservationPreference` | `'conserve' \| 'neutral'` | Frugality signal | `dataSaver === true` **or** `batteryLow === true``conserve` ||
180+
| `deliveryMode` | `'rich' \| 'cautious' \| 'lite'` | How “heavy” you should go | Derived from capability and conservation | Rich if **strong** and **not** conserving; Lite if **weak** or **conserve**; else Cautious |
181+
| `canShowRichMedia` | boolean | Convenience: `deliveryMode === 'rich'` | Derived from `deliveryMode` | Shorthand for “go big” |
182+
| `shouldAvoidRichMedia` | boolean | Convenience: `deliveryMode === 'lite'` | Derived from `deliveryMode` | Shorthand for “be frugal” |
183+
| `batteryCritical` | boolean \| null | Battery `≤ 5%` | Battery API | Mutually exclusive with `batteryLow`; `null` if unknown |
184+
| `batteryLow` | boolean \| null | `5% < level ≤ 20%` | Battery API | `true` only when not `batteryCritical` |
185+
| `batteryCharging` | boolean \| null | On charge | Battery API | `null` if unknown |

0 commit comments

Comments
 (0)