wbr 的這些那些

在 responsive design 成為主流之後,有個問題也隨之被突顯出來,就是文字的換行,尤其是標題文字的換行位置,現在的瀏覽器的換行方式簡單來說就是超出區塊範圍的東西都往下放到下一行,所以在某些情況下,就會有第一行很長第二行很短的狀況,視覺上非常不平衡,以下圖為例,網頁的副標題沒有作特殊處理,所以會有可能會變成兩行長度差距很大的樣子:

line breaking

這個問題有蠻多解決方法的,目前我知道的就有:

  1. 微調 responsvie 的樣式來避免出現不平衡的狀態
  2. 在特定地方插入 避免換行
  3. 用 flex layout 來控制換行位置

基本上就是只要你不擇手段,問題還是可以解決的,不過我一直以來都是會偏好用標準的方法來解決問題,所以整理了一下我所知道可以拿來用的東西:

  1. <wbr>HTML element
  2. white-spaceCSS property
  3. <nobr>HTML element
  4. text-wrapCSS property

首先的想法是<wbr>配上white-space: nowrap;或是<nobr>,不過意外的是大部分瀏覽器都不支援這個組合,也就是說,包在<nobr>內的<wbr>的地方在現在大部分主流瀏覽器內是不會換行的:

<nobr>ChatGPT: Optimizing<wbr>Language Models<wbr>for Dialogue</nobr>

這就激起了我的好奇心了,於是我開始仔細的找資料,看看<nobr><wbr>到底是怎樣運作的。首先,就來看看<wbr>吧,雖然他第一次出現在 W3C 的文件內就是在 HTML5,但是其實它已經出現了 20 年以上,最早是作為 Netscape 的 HTML 2 extension 的一員:

The WBR element stands for Word BReak. This is for the very rare case when you have a NOBR section and you know exactly where you want it to break. Also, any time you want to give the Netscape Navigator help by telling it where a word is allowed to be broken. The WBR element does not force a line break (BR does that) it simply lets the Netscape Navigator know where a line break is allowed to be inserted if needed.

在 Internet Archive 上找到的備份,最早的定義其實明確的說著<wbr>處應該是要可以優先於<nobr>的,我甚至還在 bugzilla 上找到一個 24 年前的 bug report 在講這件事,根據這張票最後的關掉前的討論,其實可以用</nobr><nobr>來達成一樣的效果,然後 Firefox 不打算支援<wbr>,看到這邊,我只能說這解法怎麼這麼天才(稱讚的意味)。

查到這邊,我還是很好奇為什麼現在的主流瀏覽器依然<wbr>優先度比<nobr>還低,所以繼續找資料,這次看的就是最新的文件了,首先是<wbr>定義,在 HTML5 中變成:

Thewbrelement represents a line break opportunity.

從 "word break" 變成 "line break opportunity" 了,<nobr>則和<wbr>一樣一開始是 Netscape extension,不過他倒是沒有進到 HTML5,事實上,雖然<nobr>不是 HTML5 的一員,但是文件中定義的 default style 還是有它

br { display-outside: newline; } /* this also has bidi implications */
nobr { white-space: nowrap; }
wbr { display-outside: break-opportunity; } /* this also has bidi implications */
nobr wbr { white-space: normal; }

其實就是等價於white-space: nowrap,而white-space屬性在 CSS2 時是用來定義空白的處理方式:

This property declares how white space inside the element is handled.

到 CSS Text Level 3 時定義說的更明確,是用來決定遇到 "line break opportunity" 時的處理方式和是不是要合併 space 字元的屬性:

其實在 Level 4 更是可以分開設定兩種屬性,white-space變成一個 shorthand,而看到這些定義的演進,其實也讓人發現最早命名時其實只有考慮到西方語言的特色。

看到這邊,會發現有一個新的名詞:"line break opportunity",有些地方是稱為 "wrap opportunity",這個名詞其實是出現在 CSS Module Text 文件中的,顧名思義,就是可以換行的位置,而這份文件也是定義換行邏輯的文件,不過這邊其實沒把換行演算法(line breaking algorithm)明確定義下來,而是闡明各種相關的 CSS 屬性和它們會怎樣影響換行的結果,例如換行有分強制(<br>)和非強制(<wbr>),然後不同 CSS 屬性會影響這些換行點的出現與否,至於文本之中,哪些地方可以換行,就是換行演算法的部分了,這部份在 W3C 文件沒有定義死,所以是允許瀏覽器自己決定的,不過有提供一些參考文件,像是 Unicode 的附件 14:"Unicode Line Breaking Algorithm",或是稱為 UAX14,這份文件要搭配 Unicode Database(UCD) 的 Line_Break Property 資料,文件中的第五章有仔細的說明各種不同的 line breaking class,然後資料庫則是定義了所有 Unicode 字元的 Line_Break property,接著的第六章就是最重要的換行演算法了,這邊列出了 31 條規則,基本上是反向列舉,說哪些地方不能換行,例如 WJ(word joiner)前後都不能換行,數字中間的符號前後也不能換行之類的,不過不確定哪些瀏覽器是實作 UAX14 的,Chrome 似乎有用到 UCD,Firefox 則是以 JIS X 4051 為基礎做的換行演算法,其實 JIS X 4051 是我所知道,二戰後世界,最早的正式的文字編排的標準,查到的紀錄是 1989 有一版,而 UAX14 第一個非草稿的版本則是要到 1999 年才出來。

回到現在主流瀏覽器不支援<wbr>放在white-space: nowrap裡面無法換行的問題,其實要回到這個 HTML 標籤在繪製時,是不是有 magic 的,什麼是 magic 呢?簡單說就是,你能不能用 CSS 來定義該標籤的樣子,以及你能不能用 CSS 來改掉這些預設的樣式,而<br><wbr>正好是有 magic 的標籤,可以想想看,要怎樣用 CSS 讓文字內產生一個換行點,可能有人會說剛剛上面才看到的,不過,其實那幾個display-outside的值只存在於以前的草稿中,CSS-WG 決議不為了這個問題新增多的 display 狀態,所以問題就回到 HTML-WG 這邊了,CSS-WG 的 fantasai 其實有給了一組 default style:

br { all: unset !important; display: contents !important; content: "\a" !important; white-space: pre !important; }
wbr { all: unset !important; display: contents !important; content: "\200B" !important; }

我把!important拿掉整理一下:

br {
  all: unset;
  display: contents;
  content: "\a";
  white-space: pre;
}
wbr {
  all: unset;
  display: contents;
  content: "\200B";
}

其中,\a就是換行字元\200B則是 ZWSP,zero width space,因為是 zero width,所以看不到,然後又因為是 space,所以可以用來把字切開,也就表示可以在該處斷行。不過這個版本有些問題,因為有很多瀏覽器還不支援在::before, ::after以外的物件上套用content屬性,所以 fantasai 又提供一版用::before的版本:

br, wbr { all: unset !important; display: contents !important; white-space: pre !important; }
br::before { all: unset !important; content: "\a" !important; }
wbr::before { all: unset !important; content: "\200B" !important; }

但是,實際上直接拿這組定義來用,還是一樣有問題,就是有些瀏覽器已經讓<wbr>有 magic 了,結果wbr::before是沒有用的,目前 HTML 標準的修改也就還卡在這邊(issue 則是另外一個),HTML-WG 的 Domenic 開了這個 PR 要讓<br><wbr>就用 magic 來實現效果,不過這討論已經停很久了,所以最後會是怎樣的方案還不知道。

總之目前的結論就是,現在如果想要讓<wbr>照其定義的一樣,可以在<nobr>或是white-space: nowrap內產生換行,是辦不到的,不過可以用其他的標籤來辦到,像是:

.wbr::before {
  display: inline;
  content: "\00200B";
  white-space: normal;
}

配上

<nobr>ChatGPT: Optimizing<span class="wbr">Language Models<span class="wbr">for Dialogue</nobr>

這個寫法也有出現在一份由 Leif Halvard Silli 在提交 bug 給 WebKit 所做的 test 內

到這邊,大概已經把自己控制換行位置的部分講的差不多了,不過其實,還有一個方法可以處理一開始所提到的換行結果不理想的問題,就是在 CSS Text Module Level 4 中有一個新的屬性叫做text-wrap,其中有一個屬性值是balance,合起來就是

text-wrap: balance;

這樣設定,預期的結果就是會換行換在每一行的寬度最接近的位置,不過當然還沒有瀏覽器支援,連 caniuse 都還查不到text-wrap屬性,只是還是有 JS 的解決方案:

最後的最後補上一些參考資料,一篇是古老的 IE 時代的東西,一篇則是現在的相關 CSS 屬性,一篇則是 balance wrap 的介紹:


HTML 文件圖片預設寬高比

Intrinsic aspect ratio of incomplete image

我在 2018 年有篇文章 Intrinsic Size 媒體寬高比,介紹一個標準的草案intrinsicsize,為什麼會需要這東西在那篇文章也有講,主要就是要搭配像是:

img {
  max-width: 100%;
  height: auto;
}

這種寫法非常通用,但是在圖片讀取完成前,<img />標籤的佔位會無法先知道,這會造成頁面在圖片讀取完成後瀏覽器會需要比較大的重新繪製的工作。

這幾天想起來去查了一下近況,發現竟然早早就停止發展了,caniuse 那邊 也顯示瀏覽器都把一些實驗中的支援都拿掉了,細看下去,發現 Firefox 的人當時說要開始試驗另一個比較優雅的方法,也有和 CSS WG 的人開始討論,他們當時的想法就是直接用widthheight屬性來計算寬高比(aspect ratio),不過之後就都沒有標準文件相關的發展細節或連結了。

搜尋一番後,發現 Firefox 在 71 開始就已經發佈這個修改了,當時的 release note 其實有寫,而且 MDN 上還有一篇文章:Mapping the width and height attributes of media container elements to their aspect-ratio,也有介紹這個修改要解決的問題以及技術上是怎樣處理的,簡單一點形容就是:

img { 
  aspect-ratio: attr(width) / attr(height); 
}

不過實際上不是真的使用這條 User Agent style 來實作就是了,因為這個透過widthheight計算出來的寬高比只有在圖片還沒讀取的時候會有效,圖片讀取完成後就會改成用圖片實際的尺寸來計算怎麼顯示了,所以要說它是預設的寬高比也不太正確。

在 CSS Image 這個 Module 的第四章:Sizing Images and Objects in CSS 中,有詳細的定義要怎樣決定圖片在繪製在網頁上時要怎樣處理,還定義了一些專有名詞:

  • Intrinsic dimensions 簡單一點形容就是圖片的原始尺寸,尺寸包含了寬(intrinsic width)、高和寬高比,不一定會全部都有,像是向量圖就只有寬高比,另外 intrinsic 中文翻譯是固有、或是根本的,所以 intrinsic dimentions 也不是真的就是圖片原始尺寸,它的文字敘述是:a preferred or natural size of the object itself。
  • Specified size 使用 CSS 設定的物件大小。
  • Concrete object size 根據上面兩個資訊所決定的實際上物件要繪製的大小,也就是我們眼睛所會看到的圖片呈現的大小。

所以主要就是在寫怎樣計算 concrete object size 了,大概計算過程就是和各位腦袋中想的不會差距太大。不過在這個地方,有一個細節是在 CSS 文件中故意沒有講出來的,就是怎樣取得 intrinsic dimensions,CSS 文件中沒有明確的說 intrinsic width 是來自圖片標籤的width屬性,或是圖片的實際寬度(早在 CSS 2.1 就有寫出 CSS 文件不定義怎樣取得該數值了)。其實 HTML 圖片的 intrinsic dimensions 要怎麼取得是放在 HTML 文件的 15.4.3,15 章都在講 rendering,15.4 則是 replaced elements,也就是圖片、影片之類的內容會整個替換掉的元素,15.4.3 最後一段的第一點是說圖片如果有抓下來的話就直接用圖片的 intrinsic aspect ratio,而第二點是這樣寫的:「If img'swidthandheightattribute values, when parsed using the rules for parsing dimension values), are both not an error, not a percentage, and non-zero, then use the ratio resulting from dividing thewidthattribute value by theheightattribute value.」這邊就明確的寫到用兩個屬性來計算 intrinsic aspect ratio 了,不過針對這個圖片讀取完成前的 intrinsic aspect ratio,並沒有定義一個專有名詞,所以可以看到 cnaiuse 用一個複雜的文字來敘述這項修改,要是我的話應該會把這個值命名為 intrinsic aspect ratio of incomplete image 之類的吧。

最後整理一下:

  • 圖片讀取完成前,如果有設定正確的數值的 width 和 hieght 的話,瀏覽器會先它們來計算 intrinsic aspect ratio
  • 圖片讀取完成後,瀏覽器會用圖片原始的寬高比來作為 intrinsic aspect ratio
  • 本來是希望能用在所有的 replaced elements,不過會造成既有網站壞掉所以目前限制在<img>標籤
  • 以上的修改是大約是今年前半年才進入瀏覽器的

Scroll Margin/Padding

這篇文章要介紹一組算是蠻新的 CSS 屬性,分別是 scroll-marginscroll-padding,這兩個屬性是在 Scroll Snap Module Level 1 裡面定義的,目前主要的瀏覽器都有支援,IE 完全不支援,Safari 則是還在使用舊的非標準的屬性名稱,為什麼要介紹這兩個屬性呢?因為它除了原來 scroll-snap 微調的需求之外,還順便解決了一個存在已久的問題,也就是如果網頁的設計有固定浮動在頁面頂端的 header 時(Sticky/Fixed Header),使用 link 的 fragment 直接定位到頁面特定位置的時候,最上面一部份想顯示給訪客的內容會被 header 覆蓋到。

這個問題大概是從 Sticky Header 出現時就存在了,Stack Overflow 上就可以找到十年前的發問,以前的主要的解決方法就是讓連結的目標有看不到的高度,像是用padding-top,比較乾淨一點是用 pseudo-element 的:before來把內容往下推,不過這幾個方法雖然有用,但是對我來說卻一直不是個最佳解,所以一直以來我都期待能有更漂亮的解決方案,有事沒事就會搜尋一下,然後,終於我發現了這組 CSS 屬性可以用來讓瀏覽器自己捲動到目標時有指定的偏移。

scroll-margin是用在你想要捲動到的目標元件上,也就是有 id 的那個,像我的 blog 就是<article>標籤,給它加個scroll-margin-top: 56px;就可以了:

article {
  scroll-margin-top: 56px;
}

scroll-padding則是要用在 scroll container 上的,而不是目標的父母層,舉例來說,在我這邊,沒有特別設定任何overflow的情形下,scroll container 會落在<html>元件上,而不是<article>外面的<main>,所以設定就要改成:

html {
  scroll-padding-top: 56px;
}

這組 CSS 屬性我是今年才發現的,不過其實 CSS Trick 在去年就有文章介紹了:Fixed Headers, On-Page Links, and Overlapping Content, Oh My! ,真是太後知後覺了(其實也是因為我現在沒用 RSS reader 的關係)。


ENTER or SPACE, KEYDOWN or KEYUP

前一篇文章作動行為 Activation Behavior 發佈之後,卡西又做了一些測試,發現到 ENTERSPACE 的觸發時機其實不一樣:

然後我仔細測試過發現真的是這樣,而且 SPACEkeypress的狀態,就像是滑鼠按鍵按下去但是還沒放開時的樣子,然後這又讓我有點好奇起來了,仔細搜尋一番,發現 web 標準都沒有提到這個細節的定義,唯一有一點關係的是卡西也有找到的 WAI-ARIA Authoring Practices Issue 610,於是我就覺得這應該和 Web 標準定義沒關係,應該是更古老的預設行為,於是改變方向改找 Windows 預設行為相關的文件,搜尋一陣子其實也找不太到東西,大概是因為 GUI 和 Windows 剛出的時候其實 www 還不知道在哪裡吧,不過後來還是找到兩篇 stackoverflow 的問答看起來是相關的:

總和這兩篇的內容,大概整理一下:

這個行為應該是 Windows 一開始的時候就如此設計的了(看起來是很難找到相關設計的文件),然後實際上和 ENTER 相關(相對)的操作其實是 ESC 鍵,ENTER 鍵代表的是直接點 default button(例如 form 的 submit、dialog 的 ok 之類的),或是可以說是執行元件預設的行為,至於 ESC 鍵則是取消,不過取消在網頁的控制元件中幾乎是不存在的,過去有的大概只有<select>展開下拉選單後又決定不選時可以取消,到 HTML5 則又多了<dialog>有取消的行為(關閉 dialog),大概也是因為這個原因讓人忽略了 ENTERESC 的關係,變成注意到 ENTERSPACE 都可以操作元件;至於 SPACE 鍵其實就像是滑鼠點擊,keyDown如同mouseDownkeyUp如同mouseUp,要到keyUp才算一個點擊的動作,也就是到這時候才會去觸發click事件。

搞清楚這現象的原理之後,其實也就更容易理解 WAI-ARIA Authoring Practices 的範例那些 ENTERESCSPACE 幾個按鍵行為為什麼是那樣了,當然,以後需要客製 widget 時也不用再對這幾個按鍵的行為該怎樣定義苦惱了。


作動行為 Activation Behavior

前幾天全知全能的米奧大人在 Twitter 上徵求中階的 JavaScript 課程:

然後 Jedi 提供了一個題目:

後來米奧大人真的交作業了,也有提出一些問題,然後卡西有回應:

其中,「keyup 該觸發 button 上的 onclick」這句引起了我的興趣。

為了要顧及到網頁親和力,所有的控制元件的操作都應該要可以用鍵盤執行,所以像是 button 的動作也應該要可以用鍵盤控制,但是其實我以前一直搞不清楚,這之間正確的關係應該是怎樣,就三種可能性:

  • key 事件觸發 click 事件,click 事件有 default handler
  • click 事件觸發 key 事件,key 事件有 default handler
  • click 事件和 key 事件都有同一個 default handler

當我看到卡西那段文字的時候,我覺得他應該說的是有憑據的,不過我也覺得有些不正確,像是就我的認知,button 的 key 事件預設是不會觸發 click 事件的,於是我就花了點時間研究一下網路標準,這次終於找到規範和正確的關係了。

我先從 button 標籤開始查起,然後注意到一段,在說明 button 的 activation behavior 行為應該如何的文字,行為分成 submit button、reset button 和 button 三種,其中前兩個就像是在說 submit button 和 reset button 的行為一樣,所以我就了解到,activation behavior 就是我要找的關鍵字了,目前將它翻譯為「作動行為」。

然後在 HTML 6.3 Activation 找到:

Certain elements in HTML have an activation behavior, which means that the user can activate them. This is always caused by aclickevent.

The user agent should allow the user to manually trigger elements that have an activation behavior, for instance using keyboard or voice input, or through mouse clicks. When the user triggers an element with a defined activation behavior in a manner other than clicking it, the default action of the interaction event must be to fire aclickevent at the element.

第一段就是說作動行為(activation behavior)都是click事件觸發,第二段則是說瀏覽器要讓其它方法(像是鍵盤、語音操作等)可以觸發作動行為的話,實做的方法應該是在該事件的處理器(event handler)內觸發click事件來觸發該 HTML 元素的作動行為。這段文字就可以證明卡西說的基本上沒錯,另外就是我有疑惑的,應該是keydown還是keyup事件呢?根據我自己的實驗結果應該是要用keydown,不過總還是想找一下標準定義的出處,雖然沒有找到很明確的文字說明,不過 UI Events 3.5. Activation triggers and behavior 裡面的 EXAMPLE 4 內確實是寫 keydown event,當然keydown的時間點也比較符合期待,目前在不同標準文件內看到的範例也都是用 keydown。

查到這邊大概就可以確定,正確的關係應該是「key 事件觸發 click 事件,click 事件有 default handler」,不過卡西說的小錯誤是應該要用 keydown 事件,然後我在 twitter 有回說普通 button 不應該 keydown 觸發 click 則是我當時的錯誤認知(請見 ENTER or SPACE, KEYDOWN or KEYUP)。

再來,其實我還很好奇,哪裡有定義不同的元素分別用哪些按鍵 active 呢?因為表單送出是用 ENTER 鍵,但是像是 checkbox 的狀態切換卻是用 SPACE 鍵;上面提供的幾份文件也都沒講到這部分的定義,有種刻意避開的感覺,後來又找了許久才終於找到,其實是放在 WAI-ARIA Authoring Practices 這份 Working Group Note 內,拿 checkbox 為例,在它的 Keyboard Interaction 段落內就明白寫了:

When the checkbox has focus, pressing the Space key changes the state of the checkbox.

當然也有 button 的規範,就是同時有定義spaceenter;由於這份文件是 Working Group Note,規範的硬性比較低,這應該也是故意為之的。

最後來整理一下,首先是 HTML 文件有定義,預設的作動行為都是透過click事件觸發,但是同時也要保留其它操作介面觸發作動行為的可能性,像是常見的鍵盤行為,而其它操作方式都要透過觸發click事件的方式來觸發作動行為;再來就是不同 HTML 元素的作動行為要做哪些事情也是在 HTML 文件內;至於不同 HTML 元素要支援哪些按鍵呢,這部分就要交叉參考 ARIA in HTMLWAI-ARIA Authoring Practices 兩份文件了,前者用來查詢 HTML 元素對應的 ARIA role,後者可以根據 role 來判斷要支援哪些鍵盤按鍵。

以後要做自訂的控制元件的時候,就可以正大光明的把主要的動作寫在 onclick 事件下了(然後根據情況去加上 key event)。


W3C and WHATWG and HTML

w3c-whatwg-logos

「天下大勢,分久必合,合久必分」,沒想到 W3C 和 WHATWG 之間的複雜關係就這麼突然的踏上新的里程碑了,今天 W3C blog 發表了一篇文章-W3C and the WHATWG signed an agreement to collaborate on a single version of HTML and DOM,說兩個組織已經簽好合作的協議了,未來算是要共同維護同一份 HTML 和 DOM 的 spec,HTML WG 的章程也因此要重新制訂,現在有草稿可看,詳細一點的摘要可以看 W3C CEO Jeff Jaffe 的文章 - W3C and WHATWG to work together to advance the open Web platform

  • W3C and WHATWG work together on HTML and DOM, in the WHATWG repositories, to produce a Living Standard and Recommendation/Review Draft-snapshots
  • WHATWG maintains the HTML and DOM Living Standards
  • W3C facilitates community work directly in the WHATWG repositories (bridging communities, developing use cases, filing issues, writing tests, mediating issue resolution)
  • W3C stops independent publishing of a designated list of specifications related to HTML and DOM and instead will work to take WHATWG Review Drafts to W3C Recommendations

基本上就是 W3C 相關的 WG 以後都改成貢獻到 WHATWG 那邊(在 GitHub 上),然後 W3C 那邊會拿 WHATWG 標準文件的 snapshot 來作為 CR、PR、REC,或許也可以稱為 Living Standard 的勝利。

如果要看比較細節關於兩個組織間簽的合作內容也有公開在網路上-Memorandum of Understanding Between W3C and WHATWG,裡面還有列出所有相關的 W3C 的標準文件,以前我就一直很好奇到底全部是有哪些,剛好趁這機會一次收集齊全,其實還不少我沒看過的,甚至也有 404 的(?),其中推薦標準(REC)的部分:

  1. https://www.w3.org/TR/html5/ including other URLs under this directory
  2. https://www.w3.org/TR/html50/ including other URLs under this directory
  3. https://www.w3.org/TR/html51/ including other URLs under this directory
  4. https://www.w3.org/TR/html52/ including other URLs under this directory
  5. https://www.w3.org/TR/html/ including other URLs under this directory
  6. https://www.w3.org/TR/webstorage/
  7. https://www.w3.org/TR/webmessaging/
  8. https://www.w3.org/TR/eventsource/
  9. https://www.w3.org/TR/2dcontext/
  10. https://www.w3.org/TR/dom/

非推薦標準:

  1. https://w3c.github.io/html/ including other URLs under this directory
  2. https://www.w3.org/html/wg/drafts/html/master/ including other URLs under this directory
  3. https://www.w3.org/TR/websockets/
  4. https://www.w3.org/TR/2dcontext2/
  5. https://www.w3.org/TR/microdata/
  6. https://www.w3.org/TR/staticrange/
  7. https://www.w3.org/TR/workers/
  8. https://dvcs.w3.org/hg/webperf/raw-file/default/specs/RequestAnimationFrame/Overview.html
  9. https://w3c.github.io/dom/
  10. https://www.w3.org/TR/dom41/
  11. https://www.w3.org/TR/DOM-Parsing/
  12. https://www.w3.org/TR/html53/

這些東西在 WHATWG 那邊基本上都寫在 HTMLDOM 裡面,就是單一份標準文件內塞了比較多東西這樣。

最後就是我還發現一點有趣的,WHATWG 投票那邊,四位出來投票的分別是 Apple、Microsoft、Mozilla 和 Google 四間公司的人。然後我只對 Mozilla 的 dbaron 有印象而已。


Lab Gradient

Gradient

一早起來就看到這篇文章 視覺上的完美漸層 Chromatic,介紹了一個 Sketch plugin 可以用不同色彩系統的漸層來產生更好的視覺效果,之前其實也有注意到這個問題好幾次,就是覺得 CSS gradient 的效果不好,也有注意到一些其他的漸層方法,不過一直沒記錄下來,所以趁這時候把一些資源記錄一下,該篇文章的作者 Samuel 是推薦 Lab 色彩系統的漸層。

目前因為 CSS 就是只有 RGB 漸層,所以要用其他系統的漸層就只能用模擬的,SASS 的話有 chromatic-sass,PostCSS 則是 postcss-easing-gradients,這套背後用的則是首篇文章也有介紹的 chroma.js 來轉換的,不過它其實主要是在做 easing gradient 的,然後還有一些線上的模擬工具可以讓人直接看看效果,第一個其實是 easing gradient 的工具,其實就是在 easing gradient 標準提案時有人做來讓人體驗的,另一個介面比較不 fancy,但是我覺得比較實用的 Lch and Lab colour and gradient picker


CSS 屬性排序

CSS Box

上週在弄 Stylelint 的設定,然後理所當然的處理到屬性排序的問題,以前我是用 CSScomb 的 zen 這組設定, 這組排序規則是從 Zen Coding 那邊來的,它的規則其實應該就是 Concentric CSS 的規則,concentric 是同心的的意思,同心圓的那個同心,這組規則的基本原則就是從外到內,從外部的定位、排版方式、到邊距、外框、內距、寬高等 box model 的屬性,然後才到內文的屬性,這組也是我第一次知道的有系統的 CSS 屬性排序規則,而且我覺得這個規則很直覺,而且也是很有邏輯的設計,所以就一直用到現在了。

最近這次要弄 Stylelint 設定時順便又研究了一下現在主流的排序規則,發現現在 lint tool 如果有內建排序規則的話,一定會有的其實是照字母順序排序,我自己是覺得在 CSS 領域照字母排序根本是 anti-pattern 啦,不過一直都有聽說有些大型企業的專案會用這種排序規則。事實上在 CSS-Tricks 的 Poll Results: How do you order your CSS properties? 的這篇文章,裡面就有 14% 的人是使用字母排序,當年還是 2012,2017 年 SitePoint 的調查結果 其實沒有很大改變,變成 13%,所以雖然我覺得 anti-pattern 但其實還是有一定比例的人真的這樣用,其實要說的話也還是有好處啦,因為其它的屬性排序規則都是有個基本原則,實際上要仔細排列所有屬性的時候都還是有可能會有些地方有灰色地帶。

最高比例的排序方法則是照屬性類型分類,照屬性分類其實算是比較籠統的說法,Concentric CSS 的排序規則也算是照屬性分類的,同樣符合這樣條件的另外還有 Nicolas 的 Idomatic CSS 和 Jonathan Snook 的 SMACSS 的排序規則,其實這兩種方法的大方向也和 Concentric 的很接近,雖然是分成幾個大類別,但是大類別的排序基本上一樣是從外到內的方向,然後根據 npm 的安裝數字,目前使用度最高的應該是 SMACSS 的排序建議了吧,細部的完整排序可以在 Stylelint plugin package 的 repo 那邊看到。而除了 Stylelint 之外,PostCSS 也有 plugin 叫 css-declaration-sorter 來幫你排,也內建了 Concentric CSSSMACSS,而且該 plugin 還號稱:

  • Up-to-date CSS properties fetched from the MDN Web Platform.
  • Thought-out sorting orders out of the box, approved by their authors.

感覺很不錯的樣子,它們的完整排序清單也可以在 repo 內找到:SMACSSConcentric CSS,不過排序這東西應該還是要在 lint 的時候做啊,PostCSS 的產出物通常是 production 環境用的 code 了,只是 stylelint 那邊用的排序清單和這邊的又不同,不過其實 StylelintVSCode 都可以拿 PostCSS 的來用的樣子,還沒測試過就是~

最後提兩個 Concentric CSS 灰色地帶的問題:

  1. Grid、 column、 flex、 float 這幾個屬性你會怎麼排呢~?
  2. 如果box-sizing: border-box;設下去後,width/height 要放在 padding 前面還後面呢?

SVG

SVG

最近網站的 icon 都盡量改用 SVG 向量檔了,網路上也有不少資源,像是 Material Icons,累積了一些心得可以記錄一下,其實早在 GitHub 開使用 icon font 之前,大部分的瀏覽器就都有支援 SVG 了,只不過當時的支援還不夠完備,會各自有一些問題,這應該也是 Github 當年不先用 SVG,而是用 icon font 先檔了幾年的主因,總之現在比較不用擔心這些問題了,所以 GitHub 又開槍轉用 SVG icon 了。

GitHub 的用法是 inline SVG 為主,我自己則是用<picture>比較多:

<picture>
  <source srcset="/path/to/icon.svg" type="image/svg+xml">
  <img src="/path/to/icon.png" alt="" width="32" height="32" />
</picture>

CSS Trick 有一篇文章 A Complete Guide to SVG Fallbacks 則介紹了各種在網頁內放入 SVG 圖檔的古老方法,用<picture>是比較新的,沒列在其中,而我是為了向下相容選擇用<picture>,因為支援<picture>標籤的瀏覽器都很新了,對於 SVG 的支援度很夠,剩下少數(大概都是 IE)就讓他直接吃 png 之類的(PS: pngquant 處理過的 PNG 很多可以直接在 IE6 上顯示透明色),這樣用起來就像是一般<img>一樣,SVG 檔可以先用 svgo 工具處理過,除此之外,我通常會先用 Adobe Illustrator 把不必要的圖層階層刪除,圖層在 SVG 原始碼裡面通常是會<g>標籤,另外也記得要把圖層名稱(id)改成 ASCII only 的名稱,甚至直接編輯原始碼刪除 id,因為 svgo 不會處理這部分。

用外部 SVG 檔案好處是可以善用 browser cache 減少傳輸量,尤其是大量重複使用的圖片,不過也會犧牲一些 SVG 的特性,例如會因此不能直接用 CSS 來調整樣式,做 transition,做動畫等等,所以有時候也是會用 inline SVG,就可以搭配 CSS、JS 弄出很多不錯的效果(Safari 偶爾還有遇到無解 bug),不過要讓 inline SVG fallback 回一般圖片就比較麻煩些了,在做這件事之前可以先看一下是不是有需要支援,以前是 iOS 不支援,現在還有機會碰到的大概還是 IE(6-8)吧,總之,如果有需要的話,就是參考 CSS Trick 的 SVG Fallbacks 這篇文章,使用

<svg width="96" height="96">
  <image xlink:href="svg.svg" src="svg.png" width="96" height="96" />
</svg>

這是個很有趣的技巧,首先<image>標籤是合法的 SVG 元素,所以放在 SVG 不成問題,但是不支援 SVG 的瀏覽器,照理說也不應該認得<image>標籤啊,其實,<image>在很久以前就一直是<img>的別名了,甚至在 WHATWG 的 parse HTML 文件的流程裡都還有提到處理的方式,而經過測試也都證實了以前的瀏覽器確實是這樣的行為,文裡也有各瀏覽器的行為和支援狀況,不支援 inline SVG 的就會顯示替代的 png 或 jpg 。

文章裡面還有提到背景圖使用 SVG 的處理方式,不過因為我沒這樣使用,所以沒什麼實做到,另外最近也有人用外部 SVG 加上 CSS filter 來改變 SVG icon 顏色,不過這太技巧太新了,考慮到支援度我也是沒有用過。

親和力問題的部分,一樣 CSS Tricks 有篇文章 Accessible SVG 在講相關的作法,像是用<title>加替代文字,加上role="img"等;如果要自己編寫 SVG,W3C 還有份 Authering Guide 可以參考,除了親和力相關的資訊外還有不少技巧可以參考。

最後整理一下,拿到 svg 檔案後我的處理過程:

  1. 用 Adobe Illustrator 先開來整理一下,修改圖層名稱、刪去合併不必要的圖層
  2. 輸出成 SVG 和 1x 解析度的 PNG
  3. SVG 檔用svgo最佳化、PNG 會用pngquantzopflipng處理過

使用方式則還是當成一般外部圖片為主,需要動畫效果才會用 inline SVG。


此類別所有文章