雜記 › 技術筆記 › SVG 預覽的尺寸到底聽誰的:顯示框恆等於容器,刪掉 viewBox 之後 r=1 實測 2 像素

SVG 預覽的尺寸到底聽誰的:顯示框恆等於容器,刪掉 viewBox 之後 r=1 實測 2 像素

SVG預覽有兩套尺寸口徑:「原始尺寸」欄按寬高、viewBox、再到寫死的 300 逐層回退取讀數,實際顯示框被強制成 100% 並刪掉 viewBox、恆等於容器;刪掉 viewBox 後半徑 1 的圓恆為 2 像素。

上傳後「原始尺寸」和來源檔寫著的不一樣

拿一枚 <svg viewBox="0 0 24 24"> 試預覽,「原始尺寸」那一欄寫著 24 × 24 px,可預覽框裡那圈圖形撐滿了比它大得多的區域;換成把寬高寫成 480 的,讀數跟著變成 480 × 480 px,框卻沒變。兩個數各走各的——這個頁面有兩套尺寸口徑。

「原始尺寸」這一欄只按回退鏈讀數

這個頁面就是 SVG圖片預覽 工具,它的 renderSvg 分頭走兩條路。一條把字串餵進 DOMParser,只為了算出「原始尺寸」:先看根標籤上的 width、height 屬性,缺位才取 viewBox 的第 3、4 段,viewBox 也沒有就寫死 300,最後經 parseFloat 取 Math.round 拼成「N × M px」。另一條把節點插進預覽框,先把 width、height 改成 100%,再呼叫 removeAttribute('viewBox')。兩條路的數值互不傳遞,讀數與畫出來的框常常對不上。

根標籤寫法「原始尺寸」讀數實際顯示框
width=200 height=100200 × 100 px600×400
只有 viewBox 0 0 24 2424 × 24 px600×400
width=480 與 viewBox 衝突480 × 480 px600×400
寬高與 viewBox 都缺300 × 300 px600×400
只寫 style width:200px height:150px300 × 300 px200×150

前四行裡,來源檔寫的是 200、24 還是 480 都一樣:viewBox 被刪、寬高被強制成 100% 之後,真節點的外框等於預覽容器——探針容器固定 600.00×400.00,實測每一行都落在這個值上。只有最後一行是例外:寬高寫進行內 style 時,200px、150px 壓過了鋪上去的 100%,實測框才是 200.00×150.00。所以「原始尺寸」那一欄從來不是顯示框的尺寸,它只是回退讀數。

兩套口徑:讀數走回退鏈,框恆等於容器 來源 width=200 height=100 讀數 200 × 100 框 600×400 來源 viewBox 0 0 24 24 讀數 24 × 24 框 600×400 來源 只有行內 style 讀數 300 × 300 框 200×150

圖 1:「原始尺寸」讀數與實際顯示框的兩套口徑

刪掉 viewBox 之後,r=1 的圓恆為 2 像素

viewBox 原本負責把使用者座標映射到視埠,被 removeAttribute 移除之後,使用者座標就與 CSS 像素 1:1 落地,圖形多大只由它自己的數字說了算。同一枚 <circle r="1"> 塞進 24 單位、480 單位、兩者衝突、什麼都不寫、巢狀這五種來源,走 renderSvg 之後直徑實測全是 2.00×2.00。但若不刪 viewBox、按原生方式在同一個 600×400 的框裡渲染,r=1 在 viewBox="0 0 24 24" 下量到 33.33×33.33、在 viewBox="0 0 480 480" 下量到 1.67×1.67,原生口徑下相差約 20 倍。刪掉 viewBox,等於把這 20 倍差距拉平成同一個 2.00。

縮放每檔 0.2、夾在 20% 到 500%,「適應」其實是復位

zoomSvg 每次加減 0.2,再把 svgZoom 夾住,倍率就停在 20% 到 500% 之間:從 1 開始連點放大依序是 1.2、1.4、1.6,到了 500% 之後再點原樣不動;連點縮小到了 20% 也不再改變。寫著「適應」的那顆按鈕——也就是 − 與 + 兩顆按鈕那一列最右邊的復位鍵——fit 分支無視當前倍率、直接把 svgZoom 設回 1,也就是復位成 100%,而不是貼合視窗。讀數用 Math.round(svgZoom*100),浮點 1.5999… 顯示 160%(截斷的話會是 159%)、4.999… 顯示 500%(截斷的話會是 499%),走的是四捨五入不是截斷。

「SVG 原始碼」裡的縮排,其實只在開標籤前插換行

原始碼那個 Tab 是 formatSourceCode 的產物,名字上掛著「縮排」,實測它只在每個開標籤前插入一個換行、再套上三色高亮,既不補縮排空格、也不改屬性順序、更不拆自閉合標籤。那枚 121 字元的單行樣本,在換行階段變成 6 行、129 字元,加上轉義與高亮之後 innerHTML 達 950 字元。

近半兆的大檔案,程式碼裡沒有任何上限守衛

把一份 498635 位元組、約 0.47MB、單行鋪滿 12000 個 rect 的大 SVG 餵進去,renderSvg 耗時 308 毫秒跑完,元素數量統計成 12001,顯示框仍然是 600×400。這個 12001 是由正則 /<\w+/g 逐個匹配開標籤樣式計出來的,也就是 1 個 svg 加上 12000 個 rect,閉合標籤與註解都不計。程式碼裡既沒有位元組上限也沒有元素上限的守衛,大到這種份量的檔案不會被攔下來。

失敗路徑與文案對照

失敗的提交都在清空預覽之前就提前 return,上一次的預覽原樣留著。輸入框為空時彈出「請上傳 SVG 檔案或貼上程式碼」;丟進一段沒有任何標籤的文字、或是一個沒閉合的 <svg width="10" height="10",取不到 svg 節點就彈「找不到有效的 SVG 標籤」。帶 BOM 的輸入會被 trim 剝掉,照常解析;真實的 GBK 位元組檔案端到端未實測,只能確認 readAsText 沒有傳編碼參數、預設按 UTF-8 解碼,ASCII 標籤仍然能解析。內聯腳本的對照結果:塞進 <script> 經 innerHTML 注入之後節點確實插入(計數 1)但不執行;<image> 只測了 data: 與本地相對路徑兩種 href,節點存活,像素是否真的解碼不據此斷言,全程不指向任何外部主機。

把頁面說法與實測對照,config 的介紹共 6 條可判定:上傳預覽、純前端不上傳這兩條一致;程式碼高亮、基本資訊、縮放檢視這三條部分一致——高亮其實只有換行、尺寸是回退讀數、縮放被夾在 20% 到 500%。對不上的是「即時渲染」那一句:程式碼裡沒有任何 input、paste、keyup 監聽去觸發 renderSvg,只有「渲染預覽」按鈕和檔案 readAsText 回調這兩條觸發,貼完不點按鈕,畫面不會變。

FAQ

Q:為什麼資訊欄的尺寸和預覽框裡看到的不一樣?

因為是兩套數。「原始尺寸」走 width/height→viewBox→300 的回退讀數,顯示框被強制成 100% 而且刪掉了 viewBox,恆等於容器。想知道實際畫多大,看預覽框本身。

Q:那個「適應」按鈕能把圖鋪滿視窗嗎?

不能。fit 分支只把倍率復位成 100%,不是貼合視窗。放大每點一次加 0.2,最多到 500% 就停住。

Q:預覽裡的圓忽大忽小,是不是 viewBox 寫錯了?

不是 viewBox 寫錯。是預覽在注入之前就把 viewBox 刪了,座標按 1:1 落地,r=1 恆定 2 像素。至於把 SVG 轉成圖片、以及外部字型或圖片在那種處理裡怎麼被丟掉,是另外兩條話題,本篇不展開,分別見舊文 SVG 轉 PNG 的匯出尺寸 與 SVG 的外部字型與外鏈圖片。

📎 相關文章

120 個詞槽、15 條整句、1 句寫死的開頭:lorem-ipsum 的詞表清點與重複片段實測 2026-11-05 文字分列後空欄位沒丟:a,,b 照留、列號越界填空、填 0 或 abc 退回全部列實測 2026-11-05 13 位連寫只截出 11 位:text-extract 七個內建型別的正則邊界實測 2026-11-05 1 GB 文件由 1024 塊拼成:任意大小文件生成的分塊節奏、取消邊界與三種填充位元組 2026-11-04 文件名提取的三處邊界:結尾斜槓只兜一格、查詢串整塊留下、空行靜默消失 2026-11-04