上傳後「原始尺寸」和來源檔寫著的不一樣
拿一枚 <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=100 | 200 × 100 px | 600×400 |
| 只有 viewBox 0 0 24 24 | 24 × 24 px | 600×400 |
| width=480 與 viewBox 衝突 | 480 × 480 px | 600×400 |
| 寬高與 viewBox 都缺 | 300 × 300 px | 600×400 |
| 只寫 style width:200px height:150px | 300 × 300 px | 200×150 |
前四行裡,來源檔寫的是 200、24 還是 480 都一樣:viewBox 被刪、寬高被強制成 100% 之後,真節點的外框等於預覽容器——探針容器固定 600.00×400.00,實測每一行都落在這個值上。只有最後一行是例外:寬高寫進行內 style 時,200px、150px 壓過了鋪上去的 100%,實測框才是 200.00×150.00。所以「原始尺寸」那一欄從來不是顯示框的尺寸,它只是回退讀數。
圖 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 的外部字型與外鏈圖片。