Web字型應用修煉之道(上)

動感小前端發表於2016-08-25

故事的起源,要從UED界兩大種族前端設計師和視覺設計師的愛恨說起:

設計師的視覺稿:
設計師的視覺稿

前端開發出來的真實效果:
前端開發出來的真實效果

於是戰爭爆發了:
於是戰爭爆發了

像這種視覺效果不一致問題,在日常開發中比比皆是。最近遇到的比較多的是字型問題,開了寫輪眼的設計師經常抱怨手機上的字型跟設計稿不一致,前端只能無奈的回一句手機上沒這字型啊…然而實際情況遠比這個複雜,正義的王二小見此情況決定挺身而出,踏上了Web字型修真之路,來尋找傳說中的最優解。

從0開始

這將是一次冒險,我們從0開始探索網頁中的文字是如何一步步呈現在我們眼前的。計算機的資料,本質上都是由01組成的序列,不同的序列可以傳達不同的資訊,而同樣的序列通過不同的編碼和解碼方式也會傳達不同的資訊。

我們所看到的網頁,都是從服務端網路傳輸而來的一個個資料包通過瀏覽器解析而成,網路傳輸其實是一個很複雜的編碼解碼過程,你可能聽過資料段,報文,分組,資料包,資料幀等關鍵詞,這些術語其實只是OSI模型中各個層對資料單位的不同劃分,最底層的表示還是以bit為單位的01。假設瀏覽器現在要渲染一段文字,它從服務端收到的資料包有一段資訊是這樣的(當然為了簡化,除去報文頭等資訊,假設下面這段資訊就頁面上要展示的文字資訊):

11100101 10001010 10101000 
11100110 10000100 10011111 
11100101 10110000 10001111 
11100101 10001001 10001101
11100111 10101011 10101111

這是一串位元組流,瀏覽器得到它的第一件事自然是解碼,那麼第一個問題,編碼方式很多種,瀏覽器怎麼知道用哪種方式去解碼呢?

編碼與解碼

我們所熟知的編碼方式有ASCII,GB2321,UTF-8,UTF-16等等,對於瀏覽器來說,它會按照以下規則去尋找資料的編碼型別:

  • Web 伺服器返回的 HTTP 頭中的 Content-Type: text/html; charset=”xxx”。其中charset=”xxx”就是編碼方式,當瀏覽器拿到這個資訊之後,就能愉快的解碼了;
  • 如果服務端沒有指定編碼方式,瀏覽器會去網頁檔案的head中查詢資訊,來確定編碼方式。
  • 如果還沒找到,那瀏覽器就只能自行判斷編碼了,或者讓使用者設定解碼方式。

可以看到,前兩步資訊都是確定的,只有第三步是無法確定編碼方式的。所以為了讓你的頁面能正常展示出來,一定得要在前兩步就設定好charset編碼方式,以便於瀏覽器以你期望的方式解碼。

現代網頁通常都使用utf-8的編碼方式,所以我們就以此為例。utf-8是unicode字符集的一種實現方式,unicode本質上就是一個表,一個將二進位制資料對映到各種文字元號的表,這個表野心很大,想要囊括世界上所有文字元號,並且他也實現了自己的目標,所以它也成了網路世界應用最廣泛的一個表。

假設上面那串位元組流採用了utf-8編碼,那麼根據utf-8位元組流到unicode的編碼規則:

screenshot

可以看到上面那段位元組流全都是1110xxxx 10xxxxxx 10xxxxxx的形式,那麼根據表中第三行對映關係,應該是3個utf-8位元組對應1個unicode編碼,將三個位元組中的16個x用兩個位元組表示,然後轉化成十六進位制的unicode表示,最終可得到以下結果:

11100101 10001010 10101000 -> 01010010 10101000 -> u52a8
11100110 10000100 10011111 -> 01100001 00011111 -> u611f
11100101 10110000 10001111 -> 01011100 00001111 -> u5c0f
11100101 10001001 10001101 -> 01010010 01001101 -> u524d
11100111 10101011 10101111 -> 01111010 11101111 -> u7aef

得到unicode編碼之後,我們就可以根據unicode字元表找到對應的文字元號,最終得到了以下結果:

u52a8u611fu5c0fu524du7aef -> 動感小前端

如果對最終的結果不確定,可以反向驗證一下:

escape(`動感小前端`) // "%u52A8%u611F%u5C0F%u524D%u7AEF"

得出的unicode字元數值完全一致,看來計算沒錯,那麼緊接著第二個問題來了,瀏覽器該如何去展示它?就好比我知道你的名字叫什麼,但並不知道怎麼寫一樣。

尋找字型

字型的渲染是一個很複雜的過程,首先我們需要知道在Web世界中存在著五大字型家族,江湖人稱font-family:serif、sans-serif、monospace、cursive和fantasy。在這五大家族下面,又演變出各個不同的字型,比如宋體,微軟雅黑,Arial,Helvetica等等。同樣的文字,在不同的字型下面會呈現出不同的效果:

screenshot

但是,不管是什麼字型,他們本質上都是一個表。你可以把這個表理解成三個部分:

  • 輪廓:用來記載字元的形狀;
  • 編碼:用來記載字元內部編號與字元形狀以及unicode編碼之間的對映關係;
  • 封裝:將上面這些東西封裝成特定的檔案格式

想要深入瞭解字型內部原理,請走支線劇情《Fonts & Encodings》

瀏覽器在渲染字型時,首先會把這些文字分為不同語言的小段,然後依次確定該用哪一種字型,確定之後按照字元的unicode編碼在字型中匹配相應的輪廓,並最終渲染在螢幕上。通常我們都會給頁面指定一套字型規則:

font-family: Helvetica, STXihei, "Microsoft YaHei", Arial, SimSun,sans-serif; 

瀏覽器會按照字型宣告的順序依次去尋找系統中已安裝的字型,如果找到了就按照該字型渲染,沒找到則依次往後查詢,如果最後還是沒找到,則使用瀏覽器設定的神祕的預設字型

渲染排版

確定了字型之後,瀏覽器就真的要去渲染了。如果你以為把字型設定的一樣就能萬事大吉了,那就太天真了。即使是相同的字型,不同的環境下渲染出來的結果也是不一樣的!就好比同樣是須佐能乎,不同人產生的形態也是不一樣的,先看兩張圖:

相同字型在不同環境下的效果:
相同字型在不同環境下的效果

放大後的對比效果:
放大後的對比效果

這是同一個頁面在不同環境下的顯示效果,其實如果在真實環境下看的話基本看不出來差別,但是對比一看差別還是很明顯的。MBP下是retina屏,顯示效果更細膩一些,而MBA下則更厚重些。放大來看,MBP下字型邊緣有灰色的邊緣(灰度渲染),而MBA下則是彩色的邊緣(次畫素渲染)。

可以看到,同樣是Mac系統+Chrome瀏覽器,只是版本號稍微不同,渲染效果就會有所差別。更別說在Windows和Android上了。那麼造成這種差異的原因是什麼呢?

排版引擎

不同瀏覽器有著不同的渲染引擎,不同的作業系統上面也有不同的文字排版引擎,而瀏覽器在渲染頁面文字的時候都會呼叫系統的文字排版引擎。不同的排版策略就會造成不同的渲染結果。

Mac使用的排版引擎為CoreText,Windows7為DirectWrite/GDI,Windows XP則使用GDI。我們不會深入探索各個排版引擎的原理,只需要知道不同的渲染引擎可能會造成字型有細節上的差異。即使是同一種渲染引擎,採用不同的渲染策略,比如灰度渲染和亞畫素渲染,得出的效果也是不一樣的。

Core Text 渲染引擎:
Core Text 渲染引擎

DirectWrite渲染引擎:
DirectWrite渲染引擎

GDI渲染引擎,開啟標準抗鋸齒:
GDI渲染引擎,開啟標準抗鋸齒

GDI渲染引擎,無抗鋸齒:
GDI渲染引擎,無抗鋸齒

想要深入瞭解Web字型渲染知識,可以去Typekit上刷支線劇情。

由此可看出排版引擎渲染策略的差異是造成字型顯示效果不一致的根本原因之一,但是這種差異非常之小,對於普通使用者來說,根本不會注意到這些細節,所以前端工程師大可不必為此操心。

至此,我們終於走完文字從0渲染到螢幕上的整個過程。

諸子百家

之前有提到,當瀏覽器沒有找到所宣告的字型時,會使用預設字型。問題就在於,這個預設字型到底是什麼字型呢?不同裝置之間的預設字型又分別是什麼?影響預設字型的因素又有哪些呢?

在舊PC時代,統治人類的主要是windows和mac兩大陣營,我們扳著手指頭都能列出各大平臺和瀏覽器上的預設字型。但是到了如今的無線亂世,安卓的開源讓每個裝置廠商都可能會有自己獨特的預設字型,這對網頁的視覺統一性又帶來了巨大的挑戰。

裸奔字型

裸奔字型就是你的頁面不設定任何樣式,瀏覽器呈現出的預設字型,我寫了個小demo,你可以點選試試看你瀏覽器上面的裸奔字型是啥,也可以掃碼看看手機上的情況:

screenshot

CrossBrowserTesting上跑了一下效果如下:

Win8/OSX 部分瀏覽器對比:
Win8/OSX 部分瀏覽器對比

Firefox,Safari,Chrome對比:
screenshot

很明顯能看出,裸奔字型千變萬化,根本不靠譜!

安全字型

好在,現在已經沒有人裸奔了,一般都會在頁面中手動宣告一下字型,比如百度首頁是這樣的:

body{font: 12px arial;} // 寫的這麼精簡是為了省流量麼...

谷歌首頁是這樣:

body{font-family: arial,sans-serif;} // 好歹加了字型族

天貓首頁是這樣的:

body{font: 12px/1.5 tahoma,arial,"5b8b4f53";}

淘寶首頁是這樣的:

body{font: 12px/1.5 tahoma,arial,`Hiragino Sans GB`,`5b8b4f53`,sans-serif;}

上面四種寫法可能都有自己的考慮,但僅從終端字型表現的角度來看,很明顯淘寶的寫法更專業。Arial可謂是支援性最廣的字型了,所以大家都用上了,這種被大多數系統所預設支援的字型,就是Web安全字型。

CSS Font Stack上有對Web安全字型的整理,建議設計師們在作圖的時候多考慮一下,這樣能一定程度上降低視覺差異。並且某些字型其實長得還是蠻像的,你還可以使用安全字型來代替長相相似的非安全字型。

screenshot

到目前為止,我們所做的一切考慮就是讓頁面字型效果在不同終端下儘可能保持一致,初步結論就是要使用安全字型,然而設計師並不這樣想。設計師一般會使用逼格比較高的非安全字型,比如蘭亭細黑,蘋方字型。一旦瀏覽器發現系統沒有這些字型,就會不斷降級,最壞的情況,就是一直降級到預設字型。所以通常我們會在font-family最後加上一個預設的字型族,比如sans-serif,這樣瀏覽器在最壞的情況下也能使用特定的字型族,並在該字型族下選擇一名指定字型來展示。

那麼在這些指定的種族背後,被選中的孩子們到底都有誰呢?

神祕的預設字型

首先系統會預設安裝一些字型,維基上有對Win/Mac內建字型的整理:

然後當你安裝軟體時,有可能會附帶安裝一些字型,這樣你係統上能支援的字型又變多了。在上面那份列表中,Win/Mac共同支援的字型只有Arial, Verdana, Tahoma, Trebuchet MS, Georgia等少數Web安全字型,對於Win/Mac平臺實際字型效果分析,請參考此文:

跨平臺字型效果淺析:https://isux.tencent.com/5058.html

重點說下無線端, iOS FontsiOS Font List網站整理了一份各個版本的iOS字型清單,可以很方便的查出各版本支援情況:

screenshot

雖然方便,但畢竟第三方網站,不排除資料有誤的情況,於是附上官網宣告的字型清單:

對於安卓,原生的安卓使用的是Droid Sans(英文/數字)和Droidsansfallback(中文),4.0後修改為Google的開源字型Roboto。而非原生安卓,實在沒有總結性可言。比如小米和華為用了方正蘭亭黑,錘子則使用了華文黑體,並且同一廠商下的不同手機品牌,同一品牌的不同型號預設字型都可能不同,不做展開。

一張圖總結一下:

screenshot

Windows Phone 預設英文字型是Segoe,中文字型在WP8以前是雅黑,WP8之後是方正等線體。
YunOS,貌似是方正蘭亭細黑...

強大的自定義字型

是的,使用者可以選擇自己喜歡的字型。你永遠不知道使用者會幹什麼,什麼安全字型,預設字型,一個主題包下來全都是浮雲:

screenshot

當然這不是最絕的,換個字型最多樣子變了,最絕的是使用者開啟老人機模式,放大字型!

screenshot

這兩招一出,基本會給設計師和前端造成10000+傷害,不過我們仍然可以做點什麼:

  • 嚴格控制頁面佈局,字型超出部分截斷,保證頁面正常顯示;
  • 監測頁面縮放情況並給予使用者提示;
  • 頁面自適應或者,針對老人模式單獨開發一套頁面。

小結

看到這裡王二小已經殘血,稍微修整總結一下,字型表現不一致的根本原因有:

  • 排版引擎渲染策略差異(影響小,不可規避)
  • 各終端預設字型設定差異(影響中,可規避)
  • 使用者手動設定自定義字型(影響大,不可控)

目前為止我們能做的就是儘量使用Web 安全字型,針對不同終端對font-family字型選擇順序進行優雅降級,並設定預設字型族來規避風險。

但只做到這些還遠遠不夠,我們完全處於被動狀態,一切都依賴於終端環境的字型情況,並且還沒考慮到字型格式,中英混排,字型動畫,字型優化,Web標準技術等方面。接下來我們要主動出擊,站在巨人的肩膀上去各個擊破,打怪升級,去尋找Web字型應用最佳實踐之道。

冒險越來越深入了,等待王二小的將會是什麼呢?請看下集:

screenshot

參考資料

以下是相關參考資料,若想深入瞭解,建議仔細研讀。

Web 字型的選擇和運用

https://blog.coding.net/blog/Web-Fonts

網頁字型優化

https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization?hl=zh-cn

字型的各個概念術語

http://www.zhihu.com/question/20366900 

字型渲染相關
Typekit Web字型渲染系列文章
網頁設計中預設字型詳解

https://waxdoll.gitbooks.io/webdesignfoundations/content/appendix/font_browser_default.html

Mac OS X 字型列表

https://en.wikipedia.org/wiki/List_of_typefaces_included_with_OS_X

Windows 字型列表
開源字型列表

https://en.wikipedia.org/wiki/Open-source_Unicode_typefaces

CSS Font Stack

http://www.cssfontstack.com/

數字設計之美

http://www.typeisbeautiful.com/2009/09/1467/

跨平臺字型效果淺析

https://isux.tencent.com/5058.html

對比 iOS 系統 Android 的字型渲染有何區別

https://www.zhihu.com/question/21211748

iOS Font 字型整理
Mars/font-family

https://github.com/AlloyTeam/Mars/blob/master/solutions/font-family.md

網頁字型設定你瞭解嗎?

http://ued.ctrip.com/blog/web-page-font-settings-did-you-know.html


相關文章