getClientRects() 與 getBoundingClientRect() 暴露的次像素版面配置差異可用於指紋辨識——完全不需要碰觸 Canvas。
頁面上的每一個元素,都有一個 JavaScript 能讀取到小數點後好幾位的精確位置與尺寸。讓瀏覽器回傳某個標題元素所佔據的確切矩形,得到的會是像 312.234375 這樣的浮點數——這種精細度遠超人眼所需,卻是字型微調(hinting)、次像素反鋸齒與平台特有文字排版共同作用下的產物。這些數值在不同裝置上會有細微差異,只要指令碼量測足夠多這類數值,就能把普通的頁面版面配置變成一種追蹤訊號——全程不需要 Canvas 元素,也不需要讀取任何像素。
核心要點
getClientRects()與getBoundingClientRect()回傳具有次像素(浮點數)精細度的DOMRect值——這份精細度會隨字型算繪、DPI 縮放以及作業系統文字排版引擎而變化。- 量測算繪文字的矩形是一種以版面配置為基礎的指紋技術:不需要
<canvas>元素、不需要讀取像素,也不需要任何授權彈窗,因此能繞過專門針對 Canvas 設計的防護措施。 - 它與 Canvas 指紋相關卻不相同,兩者都源自字型與算繪管線的差異;將兩者合併使用,能取得比單獨使用任一項都更高的信心水準。
- GoLogin、Dolphin{anty} 等指紋瀏覽器都內建了專門的「Rects」偽裝開關——這間接證實了業界確實會在實際偵測中檢查這項訊號。
- BrowserInsight 的指紋檢測能讓你同時看到瀏覽器目前暴露的版面配置訊號與 Canvas 訊號。
getClientRects() 究竟回傳了什麼
getClientRects() 與它較簡單的對應方法 getBoundingClientRect(),都是由 CSSOM View 規範 標準化的普通版面配置 API。它們的設計初衷,是讓 JavaScript 能回答「這個元素目前是否在可視區域內」或「這行換行文字究竟在哪裡結束」之類的日常問題,看起來完全不像追蹤工具。
對任意元素呼叫 getBoundingClientRect(),會得到單一個 DOMRect——包含 top、left、width、height 以及由此推導出的邊界值——描述它相對於可視區域的包圍框。呼叫 getClientRects() 則會回傳一整份矩形清單,每個行框(line box)對應一個矩形,這對行內內容特別重要:一句會換成三行的文字,就會回傳三個各自擁有獨立座標的矩形。Range.getClientRects() 的作用相同,只不過作用於任意一段文字選取範圍,讓指令碼能量測特定一段字元,而非整個元素。
回傳的數值並非整數,而是雙精度浮點數,因為瀏覽器的排版引擎在定位文字時採用的是次像素運算,儘管你的螢幕最終會把一切四捨五入到實體像素。這份額外的精細度——那部分永遠不會在不同裝置上以完全相同方式算繪出來的小數餘數——正是這種指紋技術的全部依據。
為什麼數值會因裝置而異
如果兩個瀏覽器用相同的 HTML 與 CSS 算繪同一段內容,矩形數值為什麼還會不同?在「這裡有一段文字」到「這是它最終的像素」之間,還有好幾層因素各自帶來了細微卻可重現的差異:
- 字型微調(hinting)與整形(shaping)。 作業系統的文字整形引擎——Windows 上的 DirectWrite、macOS 上的 Core Text、Linux 上的 FreeType/HarfBuzz——決定了字形外框究竟如何對齊到像素網格。不同的 hinting 規則,會讓同一種字型中的同一字串產生不同的前進寬度(advance width)。
- 裝置像素比。 高解析度螢幕(
window.devicePixelRatio為 2 或 3)會把次像素版面配置取整到比 1x 螢幕更精細的實體網格上,進而改變每個座標的小數部分。可參考 MDN 的devicePixelRatio文件,了解瀏覽器如何暴露這項資訊。 - 字型可用性與後備替換。 若所請求的字型未安裝,瀏覽器會替換成度量值不同的後備字型——這正是字型指紋所仰賴的機制,只不過此處的訊號是原始矩形幾何資料,而非單純的「存在/不存在」位元。
- 瀏覽器引擎與版本。 Chromium(Blink)、Firefox(Gecko)與 Safari(WebKit)各自實作了自己的文字排版與換行邏輯,因此完全相同的標記,在不同引擎之間、有時甚至在同一引擎的不同版本之間,量測結果都可能相差幾百分之一像素。
- 縮放層級與作業系統文字縮放。 頁面縮放與作業系統層級的無障礙縮放(Windows 的「放大文字」、macOS 的顯示器縮放),都會進入同一個排版管線,改變次像素餘數。
這些都不是任何人為了隱私而主動設定的選項,而是正常算繪過程的副作用——這正是為什麼最終得到的矩形,對同一裝置、同一工作階段而言是穩定的,卻在不同裝置之間產生差異。
ClientRects 指紋 vs Canvas 指紋
這兩種技術是近親——都利用了根植於字型、GPU 與作業系統文字引擎的算繪管線差異——但在觸碰的對象,以及被偵測到的難易程度上有所不同。
| 屬性 | ClientRects 指紋 | Canvas 指紋 |
|---|---|---|
| API 介面 | 普通 DOM 版面配置(getClientRects、getBoundingClientRect) | <canvas> + 2D 內容(toDataURL、getImageData) |
| 是否需要讀取像素 | 否 | 是 |
| 是否對 Canvas 專用攔截工具可見 | 否 | 是——擴充功能會掛鉤這些具體呼叫 |
| 訊號來源 | 次像素文字/元素版面配置幾何資料 | 點陣化像素顏色 |
| 典型資訊熵 | 單獨使用時中等;與字型結合時較強 | 單獨使用時較高 |
| 是否會被偵測為「讀取了一塊離螢幕 Canvas」 | 不適用——完全不涉及 Canvas | 是,可透過監控 API 呼叫偵測 |
這帶來的實際結果,正是 ClientRects 指紋對追蹤者與指紋瀏覽器廠商都相當有吸引力的原因:任何專門監視 Canvas API 呼叫的防護措施——無論是 Canvas 攔截擴充功能、farbling 層,或是我們 Canvas 指南中提到的那種監控指令碼——在這裡都無從下手,因為根本不涉及任何 Canvas。指令碼只需悄悄量測頁面上既有的普通文字與標題的版面配置,就能取得一項可相提並論的訊號。
實際的量測方式
一個典型的實作,會算繪若干帶樣式的文字元素——通常混用多種字型、字級,以及已知會觸發不同次像素取整行為的 CSS 屬性(例如 letter-spacing)——然後讀回它們的矩形:
// ClientRects 量測的示意實作
function measureRects() {
const probe = document.createElement('div');
probe.style.cssText = 'position:absolute; visibility:hidden; font-size:14px; letter-spacing:0.3px;';
probe.style.fontFamily = 'Arial, sans-serif';
probe.textContent = 'BrowserInsight fingerprint probe 0123456789';
document.body.appendChild(probe);
const rects = probe.getClientRects();
const values = Array.from(rects).flatMap(r => [r.width, r.height, r.left, r.top]);
document.body.removeChild(probe);
return values; // 與其他訊號一併送入雜湊函式
}
這個元素不需要真的可見——設為 visibility: hidden 的元素依然會參與版面配置計算,因此它產生的矩形是真實的。通常會量測並雜湊多組探測字串、字型與 CSS 組合,這與其他指紋技術普遍採用的「量測許多個小訊號、再合併起來」思路一致——這些訊號具體如何疊加成一份完整的識別碼,可參考我們的瀏覽器指紋指南。
這項技術實際出現在哪裡
相較於 Canvas 或 WebGL,這項技術在主流報導中並不常見,但在生態系統的兩個特定角落,它確實是已知的存在:
- 指紋辨識/反詐欺廠商會把以矩形為基礎的量測,當作眾多輸入訊號之一,原因很簡單:計算成本低,而且與大多數隱私工具所關注的防護方向正好正交。
- 指紋瀏覽器——這類工具專門為每個瀏覽器執行個體呈現不同卻一致的偽裝指紋——對它相當重視,甚至專門提供了對應的控制項。GoLogin 與 Dolphin{anty} 都在 Canvas、WebGL 開關旁邊,專門為偽裝或標準化「Rects」輸出提供了設定。廠商願意特地為某項訊號打造偽裝控制項,本身就是「這項訊號確實會被檢測」的一個合理佐證。
如何降低你的暴露程度
主流瀏覽器並沒有一個專門的「停用 getClientRects」開關,因為這個 API 對一般版面配置功能不可或缺——文字選取、捲動定位、工具提示,以及幾乎所有 JavaScript UI 框架都仰賴它。可用的緩解措施,與對付 Canvas 指紋的方法大同小異,因為兩者的根源相同:
- 優先選擇「趨同」而非「客製化」。 Tor 瀏覽器會標準化字型算繪,並降低若干精細版面配置 API 的精細度,藉此縮小你在其中可被區分出來的群體範圍——這與它對待 Canvas 的策略如出一轍。
- 限制已安裝字型與擴充功能的數量。 你每安裝一種額外字型,就為後備替換機制多提供一種改變矩形尺寸的可能。使用系統內建字型集合的標準安裝,比經過大量個人化調整的系統更難被單獨識別出來。
- 使用類似 resistFingerprinting 的保護機制。 Firefox 的
privacy.resistFingerprinting會對多項算繪相關數值進行取整與標準化,藉此降低這項技術所仰賴的次像素方差——這與它約束 Canvas 及 WebGL 輸出的方式相同。 - 檢查自己暴露了什麼。 執行 BrowserInsight 的指紋檢測,一次看清你的 Canvas、WebGL 及其他算繪訊號,再對比一般瀏覽器與經過隱私強化的瀏覽器的檢測結果,看看哪些設定真正發揮了作用。
與 Canvas 指紋一樣,「反指紋悖論」在這裡同樣成立:一個矯枉過正、手工打造的 Rects 偽裝——例如回傳可疑的整數,或是在每次探測中都呈現零方差的次像素結果——反而可能比它試圖隱藏的原始訊號還要顯眼。
常見問題
ClientRects 指紋與 Canvas 指紋是同一回事嗎?
不是,但兩者密切相關。Canvas 指紋是從離螢幕的 <canvas> 讀回點陣化像素;ClientRects 指紋讀取的則是普通 DOM 元素或文字的次像素版面配置幾何資料,完全不涉及 Canvas。兩者都可以追溯到字型算繪與平台文字排版的差異,因此往往彼此相關,但一個只針對 Canvas 設計的防護措施,對這項技術沒有任何阻擋作用。
Canvas 攔截擴充功能擋得住這項技術嗎?
擋不住,單靠它本身不行。Canvas 攔截器掛鉤的是 toDataURL()、getImageData() 這類 Canvas 專用 API。getClientRects() 與 getBoundingClientRect() 是完全不相關的 DOM 版面配置方法,因此專門針對 Canvas 設計的攔截器沒有理由去攔截它們。
這項技術單靠自己就能識別出我嗎?
單獨使用時通常信心水準不高——許多裝置的算繪管線相近,會產生相似的矩形結果。它對追蹤者更大的價值在於與字型、Canvas、WebGL 等其他被動屬性結合使用,這與完整指紋中每一項獨立訊號只貢獻部分資訊熵、而非完整身分的規律一致。
為什麼指紋瀏覽器會特別提到「Rects」設定?
因為這項技術確實存在,且被部分指紋辨識與反詐欺廠商實際檢測。那些致力於呈現一致偽裝身分的工具,必須像處理 Canvas 與 WebGL 一樣,對它進行標準化或隨機化——否則即使其他方面偽裝得再完美,Rects 訊號仍會洩露出真實的底層裝置資訊。
結語
ClientRects 指紋提醒我們,「防指紋」不能簡單等同於「擋住 Canvas 就萬事大吉」。任何精細到足以反映字型算繪、DPI 縮放或平台文字排版的 API,都精細到足以被用來做指紋辨識——而每個網頁應用程式早已仰賴的普通 DOM 版面配置方法,正是如此。理解其原理,才能讓你判斷某項隱私工具是否真的涵蓋了這一層面,而不是想當然地以為 Canvas 防護就是全部答案。
推薦閱讀:


