Day 24 · W4 · 無障礙線 · 難度 ★★☆☆☆
本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01。
把瀏覽器拉到 320px 寬,然後往右邊拉一下。拉得動,這條就不過。
Day 01 的承諾清單裡有一句「螢幕縮到 320px,版面會不會爆掉」,今天回收。答案先講:自家站 30 頁,零頁。然後我把六個常見的兇手一個一個放回去,看工具抓不抓得到,五個中。
一句話主軸:320px 不是哪支手機的寬度,是規範的門檻;工具只會告訴你這一頁捲了,是誰撐開的,得自己找。
WCAG 1.4.10 的原文:垂直捲動的內容,在 320 CSS 像素的寬度下不需要水平捲動。條文自己註明了這個數字的來歷:1280px 寬的畫面放大到 400%,就是 320。
所以它跟手機沒有直接關係。它管的是把畫面放大四倍的人,低視能的使用者常這樣用桌機。放大四倍之後如果每一行都要左右拉,一段文字讀完要拉幾十次。手機剛好也落在這個寬度附近,所以修了一邊另一邊也會好,但條文的出發點是放大,不是裝置。
規則庫裡 responsive 這個主題有 23 條,是 22 個主題裡最大的一組,第二名的表單 21 條。這 23 條裡,1.4.10 的這一條最硬,也最好驗:門檻是一個數字,結果是一個布林。
工具怎麼驗:開瀏覽器,把視窗設成 320px 寬,讀 documentElement.scrollWidth 跟 clientWidth。前者是內容實際多寬,後者是視窗多寬。前者大於後者,就是有東西超出去了。

兩個數字,一個比較。工具做的事就這麼多。
Day 22 跟 Day 23 那兩份 30 頁的報告,這條規則一筆都沒報。首頁、方案、聯絡、作品列表加四篇案例、部落格列表加 18 篇文章、sitemap、搜尋頁,30 頁在 320px 下都沒有橫向捲軸。
同一個家族倒是有話說:11 頁有 info,說頁面含長字串但沒設 overflow-wrap。頁沒捲,是因為那些字串目前剛好沒有長到超出 320;規則提醒的是那條防線不存在。這種 info 跟下面第二個兇手是同一件事,只是還沒發作。
這裡要講一個條件:這條只在開瀏覽器時跑。Day 04 說過 146 條裡 50 條不用瀏覽器,這條不在那 50 條裡,靜態掃描直接跳過,報告上什麼都不會有。看到沒報,先確認掃描有沒有開 --render。
零頁沒什麼好寫的。所以我做了七個示範頁。
規劃這篇的時候我列了六種最常撐開容器的寫法,每一種做成一頁,其他什麼都沒有,掃一次:

六個兇手五個中。沒中的那個是我做壞了,不是它無辜。
沒中的是表格。我第一版做了五格短文字,掃出來 320,通過,因為每一格自己換行了。換成八欄數字,數字不換行,頁寬 491,中。表格不是天生會撐開,是欄位多到換不了行才會。
長字串那頁是一段網址,1121px,六個裡最寬。中文本來就逐字換行,會撐開的是英數:網址、雜湊、沒有空白的識別碼。這種內容的修法是斷字,不是捲:overflow-wrap: anywhere 讓瀏覽器在任何字元之間都可以斷,一段網址斷在哪裡讀者都不會損失什麼。修法要看內容是什麼:文字要斷,表格要捲。格子裡的數字斷成兩行就讀錯了,所以表格是包進可捲的容器,捲動關在那個元素裡,下一節講。絕對定位那頁是 left: 300px 加 200px 寬,元素本身不大,位置把它推出去了,常見於裝飾用的圖形跟沒算好位置的提示框。
我原本以為 100vw 就是視窗寬,等於 100%。結果它多了 8px,那是捲軸的寬度。內容一長出現捲軸,100vw 還是算整個視窗,於是每一頁都多出一條捲軸寬的橫向捲動。這一個最陰險,因為版面看起來完全正常,只是右邊永遠可以再拉一點點。
這像行李箱塞到關不起來。問題通常是其中一件東西,不是整個箱子太小。
報告裡這條的內容是「內容寬 648px,視區 320px」。就這樣。它不告訴你是哪個元素。
要找元素,得回瀏覽器。把視窗縮到 320,在 console 跑這一段:
const vw = document.documentElement.clientWidth;
const hits = [];
for (const el of document.querySelectorAll('body *')) {
const r = el.getBoundingClientRect();
const spill = Math.max(r.right, el.scrollWidth + r.left); // 盒子超出,或內容在盒子裡溢出
if (spill > vw + 1) hits.push({ el, spill: Math.round(spill) });
}
// 祖先只是被子元素撐開的不算,只留最深的
hits.filter(h => !hits.some(o => o !== h && h.el.contains(o.el)))
.forEach(h => console.log(h.el, h.spill + 'px'));
第五行有兩個量。getBoundingClientRect().right 抓的是盒子本身超出視窗,寫死寬度、絕對定位、100vw 都是這種。scrollWidth 抓的是盒子沒超出、但內容在盒子裡溢出,長網址跟 pre 是這種,只看盒子會漏掉。第一版我只寫了前者,七頁裡兩頁找不到兇手,補了後者才全中。
最後那個過濾是為了不要整條祖先鏈都被列出來。八欄表格那頁,table、thead、tr 全都超出視窗,但撐開的是格子,所以只留 th。
這段不在工具裡。工具的工作是在 30 頁裡告訴你哪一頁捲了,找元素是那一頁打開之後的事。
1.4.10 有例外:需要二維版面才能使用或理解的內容。規範的說明文件舉的例子是圖片、地圖、圖表、影片、遊戲、簡報、資料表格,還有必須把工具列固定在畫面上的介面。
例外的正確用法不是「所以表格可以橫捲」,是「表格自己捲,整頁不動」。示範頁裡那張八欄表格包進 overflow-x: auto 的容器之後,頁寬回到 320,工具過了;但 console 那段還是會把 th 列出來,因為格子確實超出視窗,只是捲動被關在容器裡。兩個結果都對,量的東西不同。
台灣的碼表在這裡比 WCAG 講得更具體。1.4.10 對應的檢測碼原文:「在垂直捲動的頁面中,可水平捲動的區域面板,其設計本身也必須被容納於 320 CSS 像素的寬度內。」它直接點名「可水平捲動的區域面板」,也就是那個容器本身不能比 320 寬,裡面的東西才可以捲。Day 05 的三層模型再用一次:WCAG 給門檻,碼表把例外的邊界畫出來,工具量整頁有沒有橫捲。工具量的是第一層,碼表那句要人看容器。
程式碼區塊不在規範的例外清單上。實務上大家都讓 pre 自己捲,那是慣例,不是條文給的豁免;Day 10 講過承認限制要配替代方案,這裡的替代方案是讓長行可以換行,或至少讓捲動只發生在那個區塊。
跟 1.4.10 最常被搞混的是 1.4.4:文字放大到 200% 不能失去內容或功能。一個是縮小視窗,一個是放大文字,方向相反,壞法也不同。放大文字會壞的是寫死高度的容器,文字變大之後被截掉;縮小視窗會壞的是寫死寬度的容器。
碼表對 1.4.4 有兩條,工具都有實作,但要老實講它們是啟發式:一條看 viewport 的 meta 有沒有寫 user-scalable=no 或 maximum-scale=1,那會直接擋掉使用者放大;另一條數樣式表裡 width 用 px 的次數跟用百分比、vw 的次數,px 太多就提醒。工具沒有真的把頁面放大到 200% 再量,那要另一個探針,目前沒有。
所以這兩條在報告上是 info,不會是 fail。1.4.10 那條是真的量了才報,這兩條是看到跡象才提醒。同一份報告裡,兩種不同強度的話。自家站這兩條也都沒報:viewport 沒擋縮放,樣式表裡的 px 寬度沒多到觸發門檻。
pre、絕對定位、100vw;表格要欄位多到換不了行才會。明天 Day 25:量完站,回頭量 AI。同一個需求各寫三份。我讓 AI 先查規範再寫,差別只在它查到的那幾條。