眼看 Chrome 79 大概再兩周就要 release 啦!
對大多數人來說,這個也就是 Chrome 例行更新的一個版本。
而對我,Chrome 79 修正了一個最近一年多在我心裡佔有一席之地的 bug,我曾經努力試著解決它,最後似乎也找到自己應對它的方法。而現在它要被修掉了,多少有點感觸 😥。
本文特別想趕在 Chrome 79 正式釋出之前寫出來,讓在閱讀的你也可以一同經歷這個變化的過程。
話說從頭,可能會有點長,如果想直接看結論可以點這邊 。
Chrome 的「最小字體」傳說
我想小有接觸前端的人大概都知道 Chrome 有個 最小字體是 12px 的傳說,然而時過境遷、滄海桑田,2019 的現在還是如此嗎?
實際上現在再去 Google “chrome minimum font size” 得到的結果跟討論大都是 2013 年以前的內容,就算有稍微近期的文章,也大都是參考早期文章寫出來的。
從搜尋結果裡面可以找到這個 issue:
https://bugs.chromium.org/p/chromium/issues/detail?id=36429
裡面提到幾件重要的事情:
- Chrome 預設的最小字體是和 UI 使用的語言有關的
- 大部分的 UI 語言預設最小字體是 1px
- 複雜 UI 語言(多為亞洲語言)的預設最小字體是 10px/12px
- 2010年時還沒有辦法從設定該改最小字體的設定
而到了 2019 的現在,最小字體已經可以從設定裡面更改了,位置就在「設定」->「自訂字型」。
可能很多人會是在看到這篇文章之後才第一次打開這個設定畫面,比對一下上面列出來的項目可以發現繁中 UI 的 Chrome 的最小字體預設值就是 12px。
我猜如果從安裝時就是英文版 UI,預設值應該會是 0px/6px。
曾經是傳說的最小字體 12px,僅限於某些 UI 語言(基本上就是中文、日文、韓文、泰文)的瀏覽器。
現在其實是使用者把它調低就可以解決的問題。當然,對於開發者來說還是一個無法跨越的障礙。
做一個 fiddle 來測試一下「沒調設定」跟「調過設定」的結果: https://jsfiddle.net/pc035860/jgtfdL98/4/
Twitch 聽說你是 rem 排版大師?
自從近年各瀏覽器對 rem 的支援度有提高之後,坊間出現一些使用 rem 為單位進行網頁排版的文章,並且漸漸也有些網站開始使用這種排版方法(雖然我覺得還是相對蠻少的)。
建議往下看之前要先瞭解一下用 rem 單位進行排版大概是怎麼樣進行,可以參考下面這篇 sitepoint 的文章 https://www.sitepoint.com/understanding-and-using-rem-units-in-css/
普遍常見的起手式,是把 rem base 設為 1px 或 10px,這樣一來直接使用 rem 單位換算的過程會簡單很多。
其中又以在 <html>
上用 font-size: 62.5%
來設定為 10px 最常見:
html {
/* 16 * 0.625 = 10 */
font-size: 62.5%;
}
Twitch 的大改版衝擊
Twitch 在 2018 年 2 月左右做了一次重大改版,把原本用的 Ember.js 換成 React,這一次改版同時也把網站的排版單位改為 rem,而這也是我們繁中 Chrome 使用者的噩夢開端(好像講得有點誇張了)。
改版之後,原本頻道的資訊頁面變得非常奇怪。下面示範截圖是目前最新版,當然也還是用 rem 單位排版。
正常來說應該是這樣:
改版後就直接跑掉:
這還只是第一眼就覺得不對勁的部分。
用 Firefox 打開 Twitch 來比較就可以看出整個網站都有點不一樣,Chrome 整體似乎大了一號,這邊就單取側邊欄來對比:
WTF!? 不是, Twitch 難道沒有測過嗎…
怎麼一回事
來看一下 Twitch 的 <html>
怎麼寫的
也就是他們的 rem base 是設定為 10px,接著看看對應的 side-nav
class 寬度是多少呢?
10 * 24 = 240
,他們希望的寬度是 240px,那麼實際上在我們繁中 Chrome 顯示會是多寬呢?
288 / 24 = 12
,結果不言而喻啦!
Chrome 的最小字體被套用在
<html>
上,導致 rem 的 base 直接用 12px 起跳。
類似問題,其實也曾經在 Apple 的網站上出現過,不過當時是用 em 為單位。
眼尖的你可能會在上面的截圖中發現,字體大小是相同的。那麼我們就接著來看字體大小的部分:
14 / 1.4 = 10
剛好是設定的 rem base 10px,也就是說 font-size
部分並沒有受到 <html>
上 12px 的影響,依然故我的使用了 10px 來計算。接著我順便檢查了 padding
, margin
之類的屬性,發現結果跟上面 width
一樣是用 12px 來計算的,看來只有使用在 font-size
時候的計算是分開的。
於是我們稍微修改一下上面論述,得到一個小結論:
Chrome 的最小字體被套用在
<html>
上,導致 rem 單位使用在font-size
以外的屬性上時,其 base 直接用 12px 來做計算
這邊我做了一個簡單的驗證用 fiddle: https://jsfiddle.net/pc035860/athfj0dc/18/
裡面的 .box
,希望透過 rem 設定出的正確數值如下:
寬: 100px
高: 100px
padding: 10px
字體大小: 14px
後面也還會用到這些數值來做正確性的驗證,稍微有個印象就好了。
好啦,都是 Chrome 的錯
前面說過現在最小字體可以調整是吧,那我們把最小字體的設定調到 10px 以下不就好了嗎?
那我們就拿上面驗證用的 fiddle 來比較一下,「保持預設 12px」跟「調整到 0px」的結果:
怎…怎麼這樣,竟然沒有變!?
UI 語言的陷阱
冷靜想想,Twitch 團隊在改版過了一年多的現在都沒有修正是什麼原因?
- 無法 reproduce 問題
- 只有少部分的人有問題,不用管他們
難道問題出在語言上? 讓我們把 Chrome UI 語言設定成英文試試看。
藍框處顯示目前是中文,點擊紅框處可以切換為英文。(註: Mac 使用者需要從「系統偏好設定」->「地區與語言」裡面改)
接著再用上面的 fiddle 來測一次最小字體 0px:
沒錯,把 UI 切成英文後就一切正常了。
順帶一提,初始安裝英文 UI Chrome 的最小字體應該是 0px/6px 其中一個,可以直接得到上圖的結果。因此如果系統語言不是上述亞洲語系的話,壓根就沒有機會看到這個 bug。
總之雖然原因不明,但看起來在繁中 UI 的 Chrome 下,就算已經調整過最小字體,在
<html>
上用font-size: 62.5%
並沒有辦法把字體壓到 12px 以下。
繁中 UI 的最後一搏
看到這裡是不是你也覺得繁中 UI 的 Chrome 使用者真是太絕望了…
在我多番嘗試之後發現,剩下最後一手可以解決這個問題:
<html>
上指定 rem base 的方式直接寫成font-size: 10px
。
雖然會失去瀏覽器設定「字型大小」的彈性,但不失為一個解決辦法。
用這個更新的 fiddle 來測試: https://jsfiddle.net/pc035860/athfj0dc/20/
其實避免碰到上面這種 rem 單位排版問題的方法就是總是將 base 設為 12px 以上。雖然會造成使用時換算單位的麻煩,但總比碰到這種難以 debug 的問題來的好。
同場加映一個也碰到類似問題的網站 issue: https://github.com/squidfunk/mkdocs-material/issues/911
擴充功能: Twitch REM fix
於是我為了我往後美好的瀏覽 Twitch 體驗,我決定做將這個 workaround 做成 Chrome extension。
起初還有「要判斷當前 UI 語言之後再來套用」之類的天真(?)想法,靜下心來仔細想想,沒有問題的人根本不會來裝這個extension。
釋懷後的我,緩緩地開了個新的 CSS 檔案,在裡面輸入:
/* file: contentscript.css */
html {
font-size: 10px !important;
}
大功告成。
截圖跟寫說明花的時間還比打這段 CSS 多…
基本上這個 extension 會在 *.twitch.tv
下面無腦套用 contentscript.css
的內容,有修正版面需求的人可以到下面的 Github 網址逛逛:
https://github.com/pc035860/twitch-rem-fix
所以 Chrome 79 是?
好了,上面說了這麼多,總算可以進入正題啦!
來看這個 2013 年的 Chrome issue
https://bugs.chromium.org/p/chromium/issues/detail?id=308862
這個 issue 是我在最小字體的相關搜尋中翻到的,主訴是「rem/em 用作單位時,在計算結果前就被套用了最小字體的限制」,怎麼聽起來這麼耳熟?
Use computed font-size for em/rem resolution.
This makes us match how Firefox applies minimum font-size and makes us not break relative length computation when pages use a font-size less than the user’s minimum font-size as a basis for widths, heights, margins, padding, etc.
從中推敲一下,未修正前的版本大概可以這樣表示(純示意,我沒有去翻 source code):
const minFontSize = 12 // 瀏覽器設定「最小字體」
// 純計算後得到的 <html> font-size
const rootFontSizeComputed = 16 * 0.625 // 10
// 調整過後的 <html> font-size
const rootFontSizeAdjusted = Math.max(minFontSize, rootFontSizeComputed)
const targetNumber = 24 // 目標數字
const targetRem = 2.4 // 手算 rem 數字: 24 / 10 = 2.4
// 用於 font-size 之外的屬性: width, height, padding, margin
const resultRelativeLength = rootFontSizeAdjusted * targetRem
// Math.max(12, 10) * 2.4 = 28.8
// 用於 font-size 屬性
const resultFontSize = Math.max(minFontSize, rootFontSizeComputed * targetRem)
// Math.max(12, 10 * 2.4) = 24
這個 issue 最近終於修正完畢,在 Chrome 79 會 release。
在修正後,resultRelativeLength
的式子大致上變成這樣(純示意,我沒有去翻 source code):
// adjusted -> computed
const resultRelativeLength = rootFontSizeComputed * targetRem
// 10 * 2.4 = 24
我實際從 beta channel 安裝 Chrome 79 的測試的結果也沒有問題,真的修正啦!
什麼都不用做,只要更新到 Chrome 79 以上、上面的 extension 也不用裝,各種 rem 單位排版造成的問題就迎刃而解。
如果你常在看的網站 (例如: Twitch),突然變了(回原)樣,千萬別太驚訝喔 😉
值得特別提出的是,在 Chrome 79 的繁中 UI 上用 font-size: 62.5%
還是沒有辦法把渲染字體壓到 12px 以下,就算你調了最小字體的設定也一樣。 更多討論請見本文最後的附錄。
TL;DR
-
Chrome 的最小字體其實現在可以設定了,並且根據安裝時的 UI 語言,預設值會不同
- 中文、韓文、日文、泰文等 是 10px/12px
- 其他通常是 6px 或以下
- Twitch 改版後使用了 rem 單位排版,在繁中 UI 的 Chrome 看起來會跑掉
-
在繁中 UI 的 Chrome 底下,如果
font-size
用百分比單位表示,就算調了最小字體設定也沒有辦法被壓到小於 12px- 因此造成 Twitch 改版後版面跑掉
- 把 UI 語言調成英文可以解決這個問題
- 用
font-size: 10px
取代font-size: 62.5%
是一種 workaround - 我把 workaround 做成 extension 了
-
Chrome 79 會修正套用最小字體的方式
- rem 單位排版計算錯誤的問題會消失
- 可能有些你看習慣的網站會有變化 (例如: Twitch)
- 套用在
<html>
上時,繁中 UI 百分比font-size
的最小值依然是 12px (見附錄)
後記
從去年改版後, Twitch 版面的問題就一直困擾著我,有點像是揮不走的蒼蠅,每次打開網站就看到 😨
直到最近(2019/9)又再一次改版,新的版面搭上崩壞的 rem 單位真的是慘不忍睹,我只好弄了一個 extension 先撐著。
偶然發現下一版 Chrome release (大概兩周後)就可以躺著解決(?),發這篇文正是為了紀念這期間我內心的掙扎,還有製作擴充功能(跟它的說明)的流下的汗水。
我會懷念你的, Chrome 78 (含以前)!
感謝你讀到這邊,下次有有趣的東西要分享的話,我會再回來的。
附錄: 最終負載測試
以下的測試用 Chrome 79 beta 進行,「最小字體」都已經設定為 0 。
這次在旁邊加了使用同樣 rem 做為長寬的 div 來對照: https://jsfiddle.net/pc035860/jgtfdL98/35/
分別對 繁中 / 英文 UI 版本 以及 font-size: 10px
/ font-size: 62.5%
的組合做交叉測試,得到的結果如下:
- rem 數值用在
width
,height
上不受font-size
指定方式的影響 -
在使用者可以設定的「最小字體」之外,還有別的隱藏字體大小門檻
- 只在
font-size
用百分比設定時套用 -
根據 UI 語言會有不同的數值
- 繁中 UI 是 12px
- 英文 UI 是 9px
- 從 rem base 的角度來看,門檻應該是套用在計算完 rem -> px 結果之後,否則理應會影響我們用 rem 表示的寬跟高
- 只在
比較一下 Firefox Developer Edition 71 的測試結果:
我想 Chrome 應該是出於好意啦… 嗯…
最後把這個隱藏門檻也考慮進去,整理進前面提到的式子:
/**
* Chrome 79 繁中版
*
* 使用 font-size: 62.5% 來設定 root 字體大小
**/
// 瀏覽器 UI 最小字體隱藏門檻,只在使用百分比時啟用
// 繁中 UI: 12
// 英文 UI: 9
const minPercentageFontSize = 12
// 瀏覽器設定「最小字體」
const minFontSize = 0
// 是不是用百分比設定的: 是
const isRootFontSizePercentage = true
// font-size 屬性專用的調整函數
const adjustFontSize = value => {
// step1: 套用瀏覽器設定的「最小字體」
const step1 = Math.max(minFontSize, value)
if (!isRootFontSizePercentage) {
// 如果不是用百分比設定,不需要到 step2
return step1
}
// step2: 用百分比設定,font-size 套用隱藏門檻
const step2 = Math.max(minPercentageFontSize, step1)
return step2
}
// 純計算後得到的 <html> font-size
const rootFontSizeComputed = 16 * 0.625 // 10
// 調整過後的 <html> font-size
const rootFontSizeAdjusted = adjustFontSize(rootFontSizeComputed)
// 12
const targetNumber = 10 // 目標數字
const targetRem = 1 // 手算 rem 數字: 10 / 10 = 1
// 用於 font-size 之外的屬性: width, height, padding, margin
const resultRelativeLength = rootFontSizeComputed * targetRem
// 10 * 1 = 10
// 用於 font-size 屬性,套用調整函數
const resultFontSize = adjustFontSize(rootFontSizeComputed * targetRem)
// 12