前言
一陣子沒有關注 CSS 的最新發展,最近趁著年假補帶 2019 Google Chrome Summit 時,看到這場 talk - Next-generation web styling,裡面提到不少有趣的新屬性,透過這篇文章整理一下內容,分享給各位與未來的我。
註:官方在 12 月初也發佈了文字版本,習慣閱讀原文的讀者可以參考看看。
新世代的...CSS
- scroll-snap - native scroll inertia and decelerations
- focus-within - solving focus accessibility within elements
- @media (prefers-*) - considerately adjust your UI/UX to a user's device preferences via Media Query hooks provided by browser
- logical properties - dynamic directionality
- sticky situations - keeping UI within the viewport
- backdrop-filter - style adjustments behind an element
- :is() - formerly :any() && :matches()
- Grid-gap
- CSS Houdini - a low-level API for CSS
- typed OM - CSS values as JavaScript objects rather than strings
- Paint API - create your own paint functions using a canvas-like syntax
- animation worklet - off the main thread animation
- Others - size, aspect-ratio, min(), max(), clamp(), list style type, display: outer inner, CSS regions, CSS modules
演講中洋洋灑灑介紹了 12 種以上的 CSS 新屬性與新功能,雖然其中有幾位老朋友在我之前的文章 - CSS 魔術師 Houdini API 介紹 有說明過,但大部分的新屬性還是令人感到振奮!
scroll-snap
看到 snap 自然反應先想到薩諾斯彈指消滅一半人口,但其實字面上還有迅速回復的意思,而 scroll-snap,顧名思義就是要讓你在 scroll 時,能夠迅速回到設定的 snap point。這麼說有點抽象,回想一下你使用過的 Carousel 套件,大多數當你滑動圖片超過一半距離時,套件會自動幫你將下一張圖片拉到正中間,若是滑動小於一半距離,則會彈回至原本的圖片,就像這樣:
scroll-snap 主要提供的兩個屬性:scroll-snap-type
與 scroll-snap-align
就可以讓你直接透過 CSS 達到一樣效果,而且擁有足夠的調整彈性。
Demo:
See the Pen scroll-snap example by Arvin (@arvin0731) on CodePen.
scroll-snap-type
作用在 scroll container 上頭,用來表明要用哪種 type 的 scroll 與 scroll 的方向。(註:scroll container 指的是擁有 overflow: scroll|auto
屬性,且其內元素足以造成 overflow 的 container。)
而比起上述一般 carousel 套件預設的行為(移動小於一半距離拉回前一張圖),透過給予 scroll-snap-align
不同的屬性:start
、end
和 center
,可以自行決定 snap point,告知內容應該要對齊到 scroll container 中的哪個點。
你可以從上面的 Codepen demo link 去修改 scroll-snap-align
看看,有什麼不同結果。
另外,如果你不想讓每次 snap 都對齊邊緣,能稍微露出前一張圖的內容,可以透過 scroll-padding
與 scroll-margin
的調整來達成。不過注意,兩者的應用對象有所不同。
scroll-padding
需要應用在 scroll container 上:
.scroller {
height: 300px;
overflow-y: scroll;
scroll-snap-type: y mandatory;
scroll-padding: 40px;
}
.scroller section {
scroll-snap-align: start;
}
而 scroll-margin
則是運用在 container 內的 children 上頭:
.scroller {
height: 300px;
overflow-y: scroll;
scroll-snap-type: y mandatory;
}
.scroller section {
scroll-snap-align: start;
scroll-margin: 40px;
}
兩者效果相同:
See the Pen scroll-snap example-2 by Arvin (@arvin0731) on CodePen.
這對於你的 scroll container 中有 fixed 的元素時很有幫助:
See the Pen scroll-snap example-fixedHeader by Arvin (@arvin0731) on CodePen.
更多細節,MDN 上有蠻完整的說明。
focus-within
在Web Accessibility 的重要性這篇文章中,有提到過元素可聚焦性的重要,讓使用者能用 tab 在網頁各元素間切換。但實作上常常遇到一個問題,就是當我們利用 :focus
僞類別製作 Menu 的下拉選單,讓子元素在父元素被 focus 後顯示出來時,tab 切換會失敗,因為當你 tab focus 到子元素時,父元素就失去 focus 狀態,子元素也因應消失:
google 的圖畫說明很清楚:
但若是換成 focus-within
,就不再有這問題了,他會在父元素被 focus 時觸發:
.menu:focus-within {
display: block;
opacity: 1;
visibility: visible;
}
See the Pen focus-within-example by Arvin (@arvin0731) on CodePen.
@media (prefers-*)
Media Queries 讓我們容易實作 RWD 設計,而現在最新的 Media Queries 可以讓我們偵測到使用者 OS-level 的系統偏好設定,例如 dark-mode,或是低階設備可以開啟 prefers-reduced-motion
來降低動畫節省資源。
這些能夠自動偵測系統偏好,並給予回應的 Media Queries 有:
prefers-reduced-motion
prefers-color-scheme
prefers-contrast
prefers-reduced-transparency
forced-colors
inverted-colors
light-level
目前最新版的 Chrome (ver. 79) 已經有提供 prefers-reduced-motion
與 prefers-color-scheme
的 emulation,只要打開 Devtool,到 Rendering
tab 下就能看到:
在演講中,講者 Adam 特別強調,reduced-motion
不是 no-motion
,使用者想要少一點動畫,而非完全沒有動畫,可以從 演講中的範例感受一下(ref、codepen demo):
logical properties
身為前端工程師,或多或少都會處理 i18n 的問題,在網頁排版上,不同語言間的 writing system 可能會造成我們需要個別為不同的語言客製化設定 css style,來調整 margin、padding 等等,而 Logical properties 就是希望能用更有效率、更好維護的方式來解決這問題。 image ref
這是我們熟知的 box model:
Logical properties 將其改為:
差別在於從原本單純 top
、down
、left
、right
外,多了 block-*
與 inline-*
兩個維度,根據使用語言的不同,瀏覽器會自動調整 block-*
與 inline-*
代表的屬性,例如在英文的寫作系統上,block-start
就代表 top
,inline-end
代表 right
。
有了 logical properties 的幫助,就能簡單的依據語言系統變更 writing-mode
與 direction
來調整 layout ref:
sticky situations
Position: sticky
應該已經很多人用在產品當中了吧,但這次 Google 還是把它拿出來特別再介紹一番,提供了三種應用 sticky 的方式,蠻值得參考的:
仔細看每種應用的差別,基本上在於 sticky 的元素如何被”解除“ sticky,比較有趣的是 Sticky Desperado,利用 grid-system 達到 two column 的變化,可以到 codepen 上觀看實際程式碼:Sticky Slide、Sticky Stack、Sticky Desperado
backdrop-filter
再來是我最喜歡的一個新屬性。以往我們要達到模糊圖片背景,並在上面加上文字的效果,可能需要這樣做:
See the Pen blur image with text by Arvin (@arvin0731) on CodePen.
需要把圖片設定成 background-image
,設定 filter: blur
,再透過 position:fixed
把文字釘上去。但有了 backdrop-filter
,我們簡單將這個屬性套用在想要疊加的文字上,並直接放置於 img
元素:
See the Pen blur image with text-backdrop-filter by Arvin (@arvin0731) on CodePen.
:is()
:is()
這個僞類別已經存在很久了,但他們覺得很少人真正使用它,因此特別提出來介紹。
基本上 :is()
的功用就是能讓你將用逗號分隔的 selector,以參數形式放入其中,來達成同樣效果:
button.focus,
button:focus {
…
}
article > h1,
article > h2,
article > h3,{
…
}
/* 與上面同樣效果 */
button:is(.focus, :focus) {
…
}
article > :is(h1,h2,h3) {
…
}
gap
這邊指的 gap,就是 CSS Grid Layout 中的 gap,讓你不用為了製造出元素間的隔間,使用 margin,卻多出不必要的空隙 img ref:
在演講中他們也有提到,gap
除了能用在 Grid layout 外,FireFox 也支援將其應用在 flex
display 上頭,慣用 FireFox 瀏覽器的讀者可以嘗試看看。
CSS Houdini、typed OM、Paint API、Worklet
CSS Houdini 是由一群來自 Mozilla, Apple, Opera, Microsoft, HP, Intel, IBM, Adobe 與 Google 的工程師所組成的工作小組,志在建立一系列的 API,讓開發者能夠介入瀏覽器的 CSS engine 運作,帶給開發者更多的解決方案,用來解決 CSS 長久以來的問題:
- Cross-Browser isse
- CSS Polyfill 的製作困難
CSS Houdini 提供的 API 讓你能存取 CSS Object Model,讓你透過 Javascript 延展 CSS 的功能,比起 CSS polyfill 能有更好的效能。
而 Worklet、Typed OM、Paint API 也都包含在 CSS Houdini 的規範中,在我之前的文章 CSS 魔術師 Houdini API 介紹 都有詳細介紹過,有興趣可以前往細讀。
這邊我簡單說明 typed OM 與 Paint API 這兩個目前實作度較高的新功能,其中 Paint API 需要 Worklet 的輔助。
typed OM 簡單來說就是就是 CSSOM 的強化版,最主要的功能在於將 CSSOM 所使用的字串值轉換成具有型別意義的 JavaScript 表示形態,例如你可以這樣操作 CSS style: (source from CSS Houdini- the bridge between CSS, JavaScript and the browser)
// CSS -> JS
const map = document.querySelector('.example').styleMap;
console.log( map.get('font-size') );
// CSSSimpleLength {value: 12, type: "px", cssText: "12px"}
// JS -> JS
console.log( new CSSUnitValue(5, "px") );
// CSSUnitValue{value:5,unit:"px",type:"length",cssText:"5px"}
// JS -> CSS
// set style "transform: translate3d(0px, -72.0588%, 0px);"
elem.outputStyleMap.set('transform',
new CSSTransformValue([
new CSSTranslation(
0, new CSSSimpleLength(100 - currentPercent, '%'), 0
)]));
用 styleMap
取得元素上以物件型態表示的 style 屬性,並能透過 outputStyleMap
來設定 style,其中還可看出多了 CSSTransformValue
與 CSSTranslation
這種 Class Interface。
而 Paint API 則提供一個叫做 registerPaint 的方法::
registerPaint('simpleRect', class {
static get inputProperties() { return ['--rect-color']; }
paint(ctx, size, properties) {
// 依據 properties 改變顏色
const color = properties.get('--rect-color');
ctx.fillStyle = color.cssText;
ctx.fillRect(0, 0, size.width, size.height);
}
});
宣告使用:
.div-1 {
--rect-color: red;
width: 50px;
height: 50px;
background-image: paint(simpleRect);
}
.div-2 {
--rect-color: yellow;
width: 100px;
height: 100px;
background-size: 50% 50%;
background-image: paint(simpleRect);
}
.div-1 與 .div-2 就可以擁有各自定義寬高顏色的方形 background-image。
不過這邊要注意一下,上面撰寫的 js 檔案,你可能會覺得就直接像一般 web 嵌入 js 的方式一樣即可,
但實際上並非如此,我們需要透過 Worklets 來幫我們載入。以上面的 Paint API 為例:
// add a Worklet
paintWorklet.addModule('simpleRect.js');
// WORKLET "simpleRect.js"
registerPaint('simpleRect', class {
static get inputProperties() { return ['--rect-color']; }
paint(ctx, size, properties) {
// 依據 properties 改變顏色
const color = properties.get('--rect-color');
ctx.fillStyle = color.cssText;
ctx.fillRect(0, 0, size.width, size.height);
}
});
Worklets 可以算是給 CSS Engines 使用的 worker,相對於 web worker 來說較為輕量、生命週期較短,適合用來處理 CSS engine 這種可能會牽扯到數百萬畫素圖片的工作。
實際範例可以參考 Google 提供的 Codepen 範例
Others
還有許多新屬性在演講中沒有時間細講,就將其主要功能列在這邊,有興趣的人可以到 MDN 或 W3C 上搜尋。
size
: 可以讓你同時設定元素的寬、高屬性。aspect-ratio
: 透過此屬性,再也不用利用 padding 等方式來製造等比例縮放效果了!min()
,max()
,clamp()
: 這幾個函式提供你在各種 CSS 屬性加上數值的限制。list-style-type
: 有新的 value 可以設置,像是 emoji 與 SVGs。display: outer inner
: display 屬性之後能夠接受兩個參數,讓你明確的個別設置 outer 與 inner layout,而非使用inline-flex
這種結合在一起的 keywords。
結論
CSS 的發展總是比較緩慢,畢竟需要各個瀏覽器實作配合,背後的因素可能不單單是純技術這麼簡單,但慢歸慢,還是能看得到前進的步伐,在新屬性普及前,就到 codepen 上弄個 side project 玩玩吧!
所有演講中提到的功能,只要是目前瀏覽器已經支援的,都有範例公布在他們的範例網站上,推薦前往試試!