網站速度不只取決於主機、快取或圖片壓縮,也取決於使用者當下的裝置狀態。
同一個網站,在桌機光纖網路上可以順暢播放影片、載入動畫與第三方插件;但在手機低電量、省流量模式或網路延遲高的情況下,這些效果反而可能拖慢網站、影響瀏覽體驗,甚至讓使用者直接離開。
這時候,就可以使用類似 Obs.js 的前端偵測方式,讓網站根據使用者的網路速度、延遲、電池狀態、記憶體與 CPU 能力,自動判斷目前應該使用:
rich:完整體驗
cautious:謹慎載入
lite:輕量模式Obs.js是甚麼?
Obs.js 的主要功能是:
偵測使用者目前的瀏覽環境,然後把結果寫入
window.obs,並在<html>標籤加上對應 class,讓網站可以用 CSS 或 JavaScript 自動切換體驗。
舉例來說,如果使用者網路很好、裝置效能高,網站可以顯示影片背景、動畫、輪播、Lottie、聊天插件等完整功能。
但如果使用者網路慢、電量低、開啟省流量模式,網站就可以自動關閉重型功能,只保留主要內容,讓頁面更快、更穩定。
Obs.js 可以偵測哪些資訊?
Obs.js會使用瀏覽器提供的 API 來判斷目前環境。
常見偵測項目包含:
navigator.connection
navigator.getBattery()
navigator.deviceMemory
navigator.hardwareConcurrency1.偵測網路狀態
透過:navigator.connection可以取得:
saveData
rtt
downlink
downlinkMax這些欄位代表:
| 欄位 | 說明 |
|---|---|
saveData | 使用者是否開啟省流量模式 |
rtt | 網路延遲,單位通常是毫秒 |
downlink | 預估下載速度,單位 Mbps |
downlinkMax | 最大下載速度,部分瀏覽器才支援 |
2.偵測電池狀態
透過:navigator.getBattery() 可以取得:
level
charging通常可用來判斷:
電量 <= 20%:低電量
電量 <= 5%:嚴重低電量
是否正在充電當使用者電量很低時,網站可以減少動畫、影片、自動播放與背景特效。
3. 偵測裝置記憶體
透過:navigator.deviceMemory 可以估算裝置記憶體,例如:
navigator.deviceMemory = 4代表裝置大約有 4GB RAM。
通常可以分成:
very-low:非常低
low:低
medium:中等
high:高如果使用者裝置記憶體偏低,就不建議載入太多動畫、複雜前端效果或大型 JavaScript 套件。
4. 偵測 CPU 核心數
透過:navigator.hardwareConcurrency可以取得瀏覽器可用的CPU,例如:
navigator.hardwareConcurrency = 8代表瀏覽器可使用約 8 個邏輯核心。
通常可分成:
low:低
medium:中等
high:高如果 CPU 能力偏低,可以降低動畫頻率、減少背景運算,或關閉非必要的互動效果。
Obs.js 會在網站產生哪些結果?
Obs.js 執行後,通常會產生兩種結果。
第一種是寫入:
window.obs = {
dataSaver: false,
rttCategory: "low",
downlinkCategory: "high",
connectionCapability: "strong",
conservationPreference: "neutral",
deliveryMode: "rich",
canShowRichMedia: true,
shouldAvoidRichMedia: false,
ramCategory: "high",
cpuCategory: "high",
deviceCapability: "strong"
};第二種是把判斷結果加到 <html> 標籤上。
<html class="
has-latency-low
has-bandwidth-high
has-connection-capability-strong
has-conservation-preference-neutral
has-delivery-mode-rich
has-ram-high
has-cpu-high
has-device-capability-strong
">這樣一來,網站就可以直接用 CSS 控制畫面。
例如:
.has-delivery-mode-lite video {
display: none !important;
}
.has-device-capability-weak .heavy-animation {
display: none !important;
}可以用來控制大型圖片或動畫在低效能裝置的投放,來優化使用者體驗。
三種網站體驗模式:rich、cautious、lite
1. rich 完整模式
代表使用者目前環境良好。
通常符合:
網路速度快
延遲低
沒有開省流量
電量正常
裝置效能較好這時候可以顯示:
影片背景
大型圖片
動畫效果
Lottie
輪播特效
聊天插件
地圖 iframe
YouTube iframe
高互動元件2. cautious 謹慎模式
代表使用者環境普通,不算差,但也不適合載入太重。
通常可以保留主要功能,但延後載入或降低資源量。
例如:
圖片改用壓縮版本
動畫減少播放次數
第三方插件延後載入
輪播降低速度
iframe 改成點擊後才載入3. lite 輕量模式
代表使用者目前環境不適合載入重型資源。
可能是:
網路慢
延遲高
開啟省流量
電量過低
裝置效能較差這時候建議關閉:
影片背景
自動播放影片
大量動畫
大型輪播
Google Maps iframe
YouTube iframe
重型聊天插件
非必要追蹤工具lite 模式的目標不是讓網站變陽春,而是讓使用者能更快看到主要內容。
如何使用
掛載JS,並確保該JS優先被載入。
<script>
/*! Obs.js | (c) Harry Roberts, csswizardry.com | MIT */
;(()=>{const e=document.currentScript,i=window.obs&&window.obs.config||{},n=!1!==i.adaptive;if(n&&(!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="https://raw.githubusercontent.com/csswizardry/Obs.js/main/+e.src:"type="+e.type:"type=module");const t=document.documentElement,{connection:o}=navigator;window.obs=window.obs||{};const a=!0===i.observeChanges,r=e=>{n&&e.forEach(e=>t.classList.remove(e))},c=e=>{n&&t.classList.add(e)},s=(e,i)=>{n&&t.classList.toggle(e,i)};let l=!1;const d=()=>{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 n=!0===e.dataSaver||!0===e.batteryLow||!0===e.batteryCritical;e.conservationPreference=n?"conserve":"neutral";const t="weak"===e.connectionCapability||!0===e.dataSaver||!0===e.batteryCritical;e.deliveryMode="strong"!==e.connectionCapability||t||n?t?"lite":"cautious":"rich",e.canShowRichMedia="lite"!==e.deliveryMode,e.shouldAvoidRichMedia="lite"===e.deliveryMode,r(["strong","moderate","weak"].map(e=>`has-connection-capability-${e}`)),c(`has-connection-capability-${e.connectionCapability}`),r(["conserve","neutral"].map(e=>`has-conservation-preference-${e}`)),c(`has-conservation-preference-${e.conservationPreference}`),r(["rich","cautious","lite"].map(e=>`has-delivery-mode-${e}`)),c(`has-delivery-mode-${e.deliveryMode}`)},w=()=>{if(!o)return;const{saveData:e,rtt:i,downlink:n}=o;window.obs.dataSaver=!!e,s("has-data-saver",!!e);const t=(e=>Number.isFinite(e)?25*Math.ceil(e/25):null)(i);null!=t&&(window.obs.rttBucket=t);const a=(e=>Number.isFinite(e)?e<75?"low":e<=275?"medium":"high":null)(i);a&&(window.obs.rttCategory=a,r(["low","medium","high"].map(e=>`has-latency-${e}`)),c(`has-latency-${a}`));const l=(w=n,Number.isFinite(w)?Math.ceil(w):null);var w;if(null!=l){window.obs.downlinkBucket=l;const e=l<=5?"low":l>=8?"high":"medium";window.obs.downlinkCategory=e,r(["low","medium","high"].map(e=>`has-bandwidth-${e}`)),c(`has-bandwidth-${e}`)}"downlinkMax"in o&&(window.obs.downlinkMax=o.downlinkMax),d()},u=e=>{if(!e)return;const{level:i,charging:n}=e,t=Number.isFinite(i)?i<=.05:null;window.obs.batteryCritical=t;const o=Number.isFinite(i)?i<=.2:null;window.obs.batteryLow=o,r(["critical","low"].map(e=>`has-battery-${e}`)),o&&c("has-battery-low"),t&&c("has-battery-critical");const a=!!n;window.obs.batteryCharging=a,s("has-battery-charging",a),d()},h=()=>{if(!l){if(l=!0,w(),a&&o&&"function"==typeof o.addEventListener&&o.addEventListener("change",w),"getBattery"in navigator&&navigator.getBattery().then(e=>{u(e),a&&"function"==typeof e.addEventListener&&(e.addEventListener("levelchange",()=>u(e)),e.addEventListener("chargingchange",()=>u(e)))}).catch(()=>{}),"deviceMemory"in navigator){const i=Number(navigator.deviceMemory),n=Number.isFinite(i)?i:null;window.obs.ramBucket=n;const t=(e=n,Number.isFinite(e)?e<=1?"very-low":e<=2?"low":e<=4?"medium":"high":null);t&&(window.obs.ramCategory=t,r(["very-low","low","medium","high"].map(e=>`has-ram-${e}`)),c(`has-ram-${t}`))}var e;if("hardwareConcurrency"in navigator){const e=Number(navigator.hardwareConcurrency),i=Number.isFinite(e)?e:null;window.obs.cpuBucket=i;const n=(e=>Number.isFinite(e)?e<=2?"low":e<=5?"medium":"high":null)(i);n&&(window.obs.cpuCategory=n,r(["low","medium","high"].map(e=>`has-cpu-${e}`)),c(`has-cpu-${n}`))}(()=>{const e=window.obs||{},i=e.ramCategory,n=e.cpuCategory;let t="moderate";"high"!==i&&"medium"!==i||"high"!==n?("very-low"===i||"low"===i||"low"===n)&&(t="weak"):t="strong",e.deviceCapability=t,r(["strong","moderate","weak"].map(e=>`has-device-capability-${e}`)),c(`has-device-capability-${t}`)})()}};if("prerendering"in document&&!0===document.prerendering){const e=()=>{document.removeEventListener("visibilitychange",i),h()},i=()=>{"visible"===document.visibilityState&&e()};document.addEventListener("prerenderingchange",e,{once:!0}),document.addEventListener("visibilitychange",i)}else h()})();
//# sourceURL=obs.inline.js
</script>JavaScript 裡控制:
window.obs.deliveryMode例如,不想在 lite 模式載入 Tawk.to 聊天插件:
if (window.obs && window.obs.deliveryMode !== "lite") {
loadTawkTo();
}或是只有在 rich 模式才載入大型動畫:
if (window.obs && window.obs.deliveryMode === "rich") {
loadHeavyAnimation();
}也可以判斷是否適合載入影音內容:
if (window.obs && window.obs.canShowRichMedia) {
console.log("可以載入影片或動畫");
} else {
console.log("目前應該避免載入重型媒體");
}CSS裡控制:
在 lite 模式下關閉影片、iframe、重型動畫:
.has-delivery-mode-lite video,
.has-delivery-mode-lite iframe,
.has-delivery-mode-lite .heavy-animation,
.has-delivery-mode-lite .lottie-box,
.has-delivery-mode-lite .hero-video {
display: none !important;
}低效能裝置減少動畫:
.has-device-capability-weak * {
animation: none !important;
transition-duration: 0.01ms !important;
}省流量或低電量時,隱藏背景影片:
.has-conservation-preference-conserve .hero-video {
display: none !important;
}網路狀態良好時,顯示高品質內容:
.has-delivery-mode-rich .rich-media {
display: block;
}使用時要注意什麼?
1. 不是所有瀏覽器都支援完整偵測
例如:
Safari 不一定完整支援 navigator.connection
Firefox 支援度也有限
Battery API 在許多瀏覽器受到限制所以這段程式不能保證每個瀏覽器都取得完整資訊。
但沒有取得資料時,程式會使用預設判斷,不會導致網站壞掉。
2. 不要把它當成安全判斷
這些資料都來自瀏覽器端,不能拿來做安全驗證。
適合用途是:
前端體驗優化
效能調整
資源載入判斷不適合用途是:
會員權限判斷
付款驗證
防攻擊判斷
後端安全控制3. 不建議拿來追蹤使用者
這段程式本身沒有把資料送出去,也沒有 fetch()、XMLHttpRequest、sendBeacon() 之類的傳送行為。
但它取得的裝置資訊,例如記憶體、CPU、網路狀態,仍然可能被視為瀏覽器指紋的一部分。
因此建議只拿來做網站體驗優化,不要搭配第三方追蹤做個人識別。
官網:GitHub – csswizardry/Obs.js: Context-aware web performance for everyone · GitHub
