
一 Web動效
1 動态模糊(Motion Blur)
Motion Blur(動态模糊),這個概念也是我第一次接觸的。查了一些資料才明白,動态模糊是一種模糊效果,它隻在特動移動的時候才會讓物體模糊,通常這種模糊是在物體移動的方向上應用的,這就是當你試圖在拍一個移動的物體的效果。
當你拍攝一個物體(或是一個人),就像下圖,這個模糊就會發生,因為這個物體(人運動)移動的速度超過了相機拍攝所需的曝光時間,是以這個物體會在最終的照片中出現多次,因為它在關鍵時刻移動。
在我們平時制作動效的時候,很少會考慮(添加)動态模糊效果,另外在CSS和SVG動畫中也缺少這方面的效果,是以大多數Web動畫看起來很生硬。也就是說,如果我們給動畫在運動過程中添加一些模糊效果,會讓這種動效更貼近現實生活。
為此,@argyleink向W3C規範提出了有關于動态模糊相關的實作方案(建議)[2],他為開發者(CSSer)推薦了一種方法,在CSS的 animation 新增了 motion-rendering 和 motion-shutter-angle 。
.animated-layer {
/* GPU加速動畫 */
animation: rotate .5s linear infinite;
/* 向引擎請求動态模糊
* motion-rendering可以接受inherit | initial | auto | none | blur 值
*/
motion-rendering: blur;
/* 類似于相機的快門,指的是快門角度,用來控制模糊量或模糊強度
* motion-shutter-angle可受任意角度值 inherit | initial | auto = 180deg | [0deg, ..., 720deg]
*/
motion-shutter-angle: 180deg;
}
@keyframes rotate {
to {
transform: rotate(1turn);
}
}
來看一個簡單的示例:
從效果上看似乎沒看到動态模糊的效果。
這裡還是用文檔中提供的一個帶有動效的圖向大家展示動态模糊的效果:
雖然說, motion-rendering 和 motion-shutter-angle 還隻是一個提案,離到TR階段還需要很長的時間,但對于某些場景(動畫場景),可以使用CSS的filter來模拟,比如下面這個效果:
代碼很簡單:
.blur {
animation: blur 250ms;
}
@keyframes blur {
0% {
-webkit-filter: blur(0px);
}
50% {
-webkit-filter: blur(5px);
}
100% {
-webkit-filter: blur(0px);
}
}
另外,現在提供的 motion-rendering 和 motion-shutter-angle 還隻是一個提議,在Github中讨論的評論中,也有建議将這兩個屬性換成:
filter: motion-blur(5px) motion-squash(2px)
// 或
transform-fiilter: motion-blur(180deg)
// 或
transition-filter: motion-blur(180deg)
也就是說,上面提供的CSS實作動态模糊的屬性不是完全定下來的,随着後面的發展,CSS中實作動态模糊的屬性,一切皆有可能。
再來看看SVG世界中,相對于CSS世界而言,SVG中要實作動态模拟效果要更容易一些,可以使用SVG中的濾鏡來模拟動态模糊效果:
上圖來自于@Lucas Bebber的《Motion Blur Effect with SVG》[3]教程。
如果你對SVG中的濾鏡相關的知識感興趣的話,可以閱讀:
- SVG 1.1: Filter Effects[4]
- SVG Filters 101[5]
有意思的是,@Michelle Barker在Codepen寫了一個Demo,這個Demo是用CSS的box-shadow模拟出有動态模拟的效果[6]:
如果你真的想在項目中讓自己的動畫效果具有動态模糊效果(讓動效看上去更真一點),而又擔心CSS或SVG相關特性未得到主流浏覽器支援而不敢使用,那麼我在這裡向大家推薦一個JavaScript庫:MotionBlurJS:
來看使用MotionBlurJS實作的動态模糊效果:
2 @scroll-timeline
Web開發者時常會碰到使用滾動來觸發某些元素的動畫效果,比如說,頁面滾動條滾動到某個位置,标題固定在頂部;頁面頂部展示你頁面進度(滾動訓示器);還是一些我們所說的視差滾動效果等。以往實作這些效果,大都借助JavaScript來實作,可以通過DOM事件檢視滾動位置,并根據該位置更改元素的樣式。如果可以的話,最好使用 IntersectionObserver。有關于這方面的介紹可以閱讀:
- Trust is Good, Observation is Better—Intersection Observer v2
- How to do scroll-linked animations the right way
不過,現在有一個關于這方面的CSS草案,即 Scroll-linked Animations。也就是說,在未來,我們可以直接使用CSS的 @scroll-timeline 屬性來實作前面提到的一些動畫效果。
@scroll-timeline = @scroll-timeline <timeline-name> { <declaration-list> }
在規範中向大家提供了兩個簡單的示例,比如說,兩個圓球的碰撞動效。這個動效由滾動位置來控制,簡單地說,随着頁面往下滾動,左右兩個球慢慢向中間靠齊,直到他們兩碰撞到一起,變成一個圓。反之,頁面往上滾動時,中間的圓慢慢的會分離出左右兩個圓。
CSS代碼大緻像下面這樣:
@media (prefers-reduced-motion: no-preference) {
div.circle {
animation-duration: 1s;
animation-timing-function: linear;
animation-timeline: collision-timeline;
}
#left-circle {
animation-name: left-circle;
}
#right-circle {
animation-name: right-circle;
}
#union-circle {
animation-name: union-circle;
animation-fill-mode: forwards;
animation-timeline: union-timeline;
}
@scroll-timeline collision-timeline {
source: selector(#container);
orientation: block;
start: 200px;
end: 300px;
}
@scroll-timeline union-timeline {
source: selector(#container);
orientation: block;
start: 250px;
end: 300px;
}
@keyframes left-circle {
to { transform: translate(300px) }
}
@keyframes right-circle {
to { transform: translate(350px) }
}
@keyframes union-circle {
to { opacity: 1 }
}
}
如果是使用JavaScript的話,可以像下面這樣:
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
const scrollableElement = document.querySelector('#container');
const collisionTimeline = new ScrollTimeline({
source: scrollableElement,
start: CSS.px(200),
end: CSS.px(300)
});
const left = leftCircle.animate({ transform: 'translate(300px)' }, 1000);
left.timeline = collisionTimeline;
const right = leftCircle.animate({ transform: 'translate(350px)' }, 1000);
right.timeline = collisionTimeline;
const union = unionCircle.animate({ opacity: 1 }, { duration: 1000, fill: "forwards" });
union.timeline = new ScrollTimeline({
source: scrollableElement,
start: CSS.px(250),
end: CSS.px(300)
});
}
再來看一個滾動計時器的效果[7]:
上面的示例,我們是使用漸變來模拟的一個效果,但有了 @scroll-timeline 我們就可以像下面這樣來實作:
@media (prefers-reduced-motion: no-preference) {
@scroll-timeline progress-timeline {
source: selector(#body);
start: 0;
end: 100%;
}
@keyframes progress {
to { width: 100%; }
}
#progress {
width: 0px;
height: 30px;
background: red;
animation: 1s linear forwards progress progress-timeline;
}
}
如果使用Web Animation API的話,可以像下面這樣:
if (window.matchMedia('(prefers-reduced-motion: no-preference)').matches) {
var animation = div.animate({ width: '100%' }, { duration: 1000, fill: "forwards" });
animation.timeline = new ScrollTimeline(
{
start: 0,
end: CSS.percent(100)
}
);
}
@argyleink在他分享的PPT中也提供了一個簡單的示例[8],滾動頁面的時候,你會發現兩個數字之間的/符會不斷的旋轉:
上面的示例效果是基于Web Animation API來實作 @scroll-timeline 的效果,但是目前還需要其對應的Polyfill[9]。
import 'https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js'
const counter = document.querySelector('main')
const slashes = document.querySelectorAll('.slash')
slashes.forEach(slash => {
slash.animate({
transform: ['rotateZ(0)','rotateZ(4turn)']
},{
duration: 1000,
fill: 'both',
timeline: new ScrollTimeline({
scrollSource: counter,
fill: 'both',
}),
})
})
有關于 @scroll-timeline 更詳細的介紹可以查閱 Scroll-linked Animations,另外,該規範目前還隻是一個草案,在未來有可能還會有所變動。
二 Web排版
先看布局上将會有的一些新特性:
1 subgrid
CSS Grid布局是Web布局模式中唯一一種二維布局,也是我自己最認可的布局模式(至少到目前為止還沒有發現比Grid更強大的)。如果你從未接觸過Grid布局的話,你可以把他想象成最初的table布局,因為他們倆之間有很多概念都非常的相似。
随着Web布局技術不斷的更新以及浏覽器不斷的發展,現在使用Grid布局的越來越多,特别是今年以來,Grid和Flexbox布局的占比越來越近:
上面的資料是來自于MDN,更詳細的可以閱讀MDN Browser Compatibility Report 2020[10]。
暫時把你拉回到90年代,那個時候Web的布局主要以table布局為主,在使用table布局的時候也時常會發現表格嵌套表格:
在Grid布局也是相似的,也會碰到網格嵌套網格。
<!-- HTML -->
<div class="grid">
<div class="item">
<div class="subitem"></div>
</div>
</div>
/* CSS */
.grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(4, minmax(100px, auto));
}
.item {
grid-column: 2 / 7;
grid-row: 2 / 4;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 80px);
}
.subitem {
grid-column: 2 / 4;
grid-row: 1 / 4;
}
網格嵌套在網格中,各自的網格軌道是互相獨立的,不過也會引起子網格中元素對齊會存在一些問題。不過在 CSS Grid Layout Module Level 2 子產品中新增了 subgrid 屬性(Firefox 71開始就支援該屬性)。
有了 subgrid 之後,在嵌套網格的時候,我們就可以在 grid-template-columns 和 grid-template-rows 設定 subgrid。這樣一來,上面示例的代碼我們就可以修改成:
.grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(4, minmax(100px, auto));
}
.item {
grid-column: 2 / 7;
grid-rows: 2 / 4;
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
}
.subitem {
grid-column: 3 / 6;
grid-row: 1 / 4;
}
這樣一來,子網格就會繼承其父網格的網格軌道,反過來,在使用任何類型的自動調整(比如,auto、min-content、max-content 等)時也會影響其次元(尺寸)。
在我們平時的一些UI布局中,subgrid 就可以用得上了:
我們一起來看一個subgrid的具體執行個體[11]。注意,請使用Firefox 71+檢視上面的Demo,看到的效果如下:
subgrid和grid一樣,是一套複雜的體系,如果要說清楚subgrid的話,可能會要多文章文章才能講清楚。如果你對subgrid感興趣的話,還可以閱讀下面這幾篇文章:
- CSS Grid Layout Module Level 2: subgrid
- Hello subgrid!
- CSS Grid Level 2: Here Comes Subgrid
- MDN: Subgrid
- Irregular-shaped Links with Subgrid
2 可用于雙螢幕和可折疊螢幕的媒體查詢條件和環境變量
随着技術不斷的發展,我們所面對的終端個性化越來越強,比如現在市場上已有或将有的雙螢幕和可折疊螢幕裝置:
作為Web開發者,我們終究有一天需要面對這些終端的适配處理。到目前為止,CSS世界具備處理方面适配的能力,即使用 screen-spanning 媒體查詢條件和 env(fold-left)、env(fold-top)、env(fold-height) 及 env(fold-width) 環境變量:
有了這些特性,我們就可以很輕易的實作像下圖這樣的布局效果:
3 瀑布流布局
瀑布流布局(Masonry Layout)也是Web布局中的典型布局之一:
雖然能使用CSS的多列布局、Flexbox布局和Grid布局等模拟出瀑布流布局效果,但更多的同學還是更喜歡使用一些JavaScript庫來實作瀑布流布局,比如 Masoonry。
為了能讓原生的CSS直接實作瀑布流布局效果,早在2017年社群中就有人提出用原生的CSS實作瀑布流布局效果,不幸的是,直到現在也還隻是一個實驗性的屬性,而且僅在Firefox Nightly浏覽器中支援。
.masonry {
display: grid;
gap: 20px;
grid: masonry / repeat(auto-fill, minmax(250px, 1fr));
}
比如這個Demo[12]。
為了能在Firefox Nightly浏覽器能正常的檢視上面Demo的效果,你需要確定開啟了相應的功能。如果沒有的話,請在Firefox Nightly浏覽器位址欄中輸入 about:config,然後搜尋 layout.css.grid-template-masonry-value.enabled,并将其設定為true:
然後重新開機浏覽器,檢視Demo,你看到的效果将會是像下面這樣:
4 gap
“Gap”從字面上來解釋的話可以有“間隙,間隔”之意。那麼在Web的布局中總是避免不了處理塊與塊以及行與行之間的間距。
而在CSS的世界中,用來控制元素之間的間距的間距,一般會使用盒模型中的外距,即 margin 屬性,但是往往很多時候,使用margin來控制元素之間間距并不能很好的滿足設計師的訴求。比如說,元素隻和元素之間有間距,但和它的父容器之間沒有任何的間距。針對于這樣的場景,使用gap屬性會比使用margin要容易控制的多。
注意,上圖來自于《Next-generation web styling》[13]一文。
CSS的 gap 屬性自身最大的特點就是:gap 是相對于流的,這意味着它會根據内容流的方向動态變化。比如說書寫模式的改變,gap 也會自動改變。
早期在CSS中,gap 分很多種,在不同的容器格式中,叫法不同,比如在多列布局(Multi-column Containers)中對應的是column-gap:
body {
column-gap: 35px;
}
但在網格容器(Grid Containers)又被稱為 grid-row-gap 和 grid-column-gap。
除此之外,它還可以被運用于Flexbox容器(Flexbox Containers),隻不過早前,在Flexbox中沒有類似 flex-row-gap 和 flex-column-gap 這樣的屬性。
需要注意的是,在Flexbox子產品中是沒有gap屬性,但這并不影響我們在Flexbox布局中使用gap屬性,這是因為gap統一納入到了 CSS Box Alignment Module Level 3子產品。而且gap是row-gap和column-gap的簡寫屬性:
我們現在可以在多列布局,Flexbox布局和網格布局中像下面這樣使用 gap:
// 多列布局
.multi__column {
gap: 5ch
}
// Flexbox布局
.flexbox {
display: flex;
gap: 20px
}
// Grid布局
.grid {
display: grid;
gap: 10vh 20%
}
從上面示例代碼中我們可以發現,gap 是 row-gap 和 column-gap 的簡寫屬性,而且 gap 可以接受一個值也可以接受兩個值,當 gap 隻有一個值時,表示 row-gap 和 column-gap 的值相同;當 gap 有兩個值時,其中第一個值是 row-gap,第二個值是 column-gap。
.gap {
gap: 10px;
}
// 等同于
.gap {
row-gap: 10px;
column-gap: 10px
}
.gap {
gap: 20px 30px;
}
// 等同于
.gap {
row-gap: 20px;
column-gap: 30px;
}
特别聲明一點,雖然CSS新增了gap屬性(row-gap、column-gap),但Grid中早期的grid-row-gap和grid-column-gap屬性同樣可用。
5 aspect-ratio
aspect-ratio 是 CSS Box Sizing Module Level 4 子產品中的一個用來計算元素盒子寬高比的屬性。在這個屬性還沒有之前,在CSS中都是通過其他一些方法來模拟寬高比的效果。比如:
.aspectration {
position: relative;/*因為容器所有子元素需要絕對定位*/
height: 0; /*容器高度是由padding來控制,盒模型原理告訴你一切*/
width: 100%;
}
.aspectration[data-ratio="16:9"] {
padding-top: 56.25%;
}
.aspectration[data-ratio="4:3"]{
padding-top: 75%;
}
如果浏覽器支援了aspect-ratio的話,可以像下面這樣使用:
div {
aspect-ratio: 1 / 1;
}
比如@rachelandrew在Codepen提供的一個示例[14],我在該示例的基礎上稍作調整了一下:
6 :target 和 :target-within
:target和:target-within都是 Selectors Level 4 子產品中的兩個僞元素。可能很多同學對:target更熟悉一些,甚至用:target僞元素的特性制作了 Tab 、Accordion 和 Modal 等UI互動效果。
比如下面這個手風琴的效果就是用:target僞元素制作的:
這裡簡單的來看看:target和:target-within的作用。
在某些文檔語言中,文檔的URL可以通過URL的片段進一步指向文檔中的特定元素。以這種方式指向的元素是文檔的目标元素。其中片段辨別符是URL中緊跟#的部分,例如#top或#fontnote1。你可能已經使用它們建立頁面内導航,比如大家常見的“跳轉連結”。有了:target僞類,我們可以突出顯示與該片段對應的文檔部分,而且無需JavaScript也可以做到這一點。
比如下面這個簡單的示例:
<!-- HTML -->
<h3>Table of Contents</h3>
<ol>
<li><a href="#p1">Jump to the first paragraph!</a></li>
<li><a href="#p2">Jump to the second paragraph!</a></li>
<li><a href="#nowhere">This link goes nowhere, because the target doesn't exist.</a></li>
</ol>
<h3>My Fun Article</h3>
<p id="p1">You can target <i>this paragraph</i> using a URL fragment. Click on the link above to try out!</p>
<p id="p2">This is <i>another paragraph</i>, also accessible from the links above. Isn't that delightful?</p>
/* CSS */
p:target {
background-color: gold;
}
/* 在目标元素中增加一個僞元素*/
p:target::before {
font: 70% sans-serif;
content: "►";
color: limegreen;
margin-right: .25em;
}
/*在目标元素中使用italic樣式*/
p:target i {
color: red;
}
你可以看到像下圖的效果:
而:target-within僞類應用于:target僞類所應用的元素,以及在平面樹(Flat Tree)的後代(包括非元素節點,比如文本節點)與比對:target-within的條件相比對的元素。
article:target-within {
background-color: hsl(var(--surfaceHSL) / 10%);
}
其實在選擇器Level 4子產品中還新增了很多其他的僞類選擇器,如果你對這方面新增的選擇器感興趣的話,可以聽一聽@Adam Argyle和@Ana Tudor一起辦的CSS Podcast,其中第十四期[15]就是專門聊CSS的僞類選擇器的。
7 CSS邏輯屬性
如果你接觸過CSS書寫模式特性,你會發現以前我們熟悉的實體屬性在不同的語言環境之下很難滿足布局的需求,比如英語和阿拉伯語,日語和蒙語等,我們設定的margin-left有可能不是margin-left,width也有可能不是width。
這個時候,CSS邏輯屬性就顯得尤其重要。換句話說,邏輯屬性的出現,我們以往熟悉的盒模型也将帶來很大的變化:
下圖是我們熟悉的實體屬性和邏輯屬性的對應關應:
對于塊軸(block axis)和内聯軸(inline axis)差別,同樣用一張圖來描述這兩者吧:
塊軸和内聯軸和CSS的書寫模式writing-mode以及direction和HTML的dir有關系。換句話說:
- 塊軸:主要定義網站文檔(元素塊)流,CSS的書寫模式writing-mode會影響塊軸的方向。
- 内聯軸:主要定義網站的文本流方向,也就是文本的閱讀方式,CSS的direction或HTML的dir會影響内聯軸的方向。
8 min()、max()和clamp()
min()、max()和clamp()三個函數稱為“比較函數”。早在《聊聊min(),max()和clamp()函數》一文中對其做過詳細的介紹。這裡不做詳細介紹,僅和三張圖來分别展示他們的功能。
我們可以使用 min()設定最大值:
max()和min()相反,傳回的是最大值。使用max()設定一個最小值:
clamp()函數和min()以及max()不同,它傳回的是一個區間值。clamp()函數接受三個參數,即 clamp(MIN, VAL, MAX),其中MIN表示最小值,VAL表示首選值,MAX表示最大值。它們之間:
- 如果VAL在MIN和MAX之間,則使用VAL作為函數的傳回值;
- 如果VAL大于MAX,則使用MAX作為函數的傳回值;
- 如果VAL小于MIN,則使用MIN作為函數的傳回值。
這裡有一個關于clamp() 的示例,嘗試着拖動浏覽器視窗的大小,你可以看到類似下圖這樣的效果:
我們再來看幾個和文本相關的特性:
9 leading-trim 和 text-edge
一直以來,在Web的排版中行高(line-height)總是令Web開發者感到困惑和頭痛,主要是因為line-height在CSS中是一個非常複雜的體系。他的計算總是會涉及到很多因素:
@iamvdo的《Deep dive CSS: font metrics, line-height and vertical-align》一文對這方面做過深入的闡述!
在還原UI時,文本的行高總是讓我們計算元素塊之間的間距帶來一定的麻煩:
為了解決這方面的煩惱, CSS Inline Layout Module Level 3新增了一個leading-trim和text-edge屬性。可以讓我們删除每一種字型中的額外間距,以便我們可以更好的計算相鄰塊元素之間的間距。
h1 {
leading-trim: both;
text-edge: cap alphabetic;
}
上面的示例首先使用text-edge來告訴浏覽器想要的文本邊緣是cap高度和字母基線(alphabetic baseline)。然後用leading-trim對文本兩邊進行修剪。
注意,leading-trim隻影響文本框,它不會切斷其中的文字。
示例中的兩行簡單的CSS建立了一個包含文本的幹淨的文本框(不受line-height相關的特性影響)。這有助于實作更精确的間距,并建立更好的視覺層次結構。
CSS的text-edge和leading-trim分别可接受的值:
text-edge: leading | [ text | cap | ex | ideographic | ideographic-ink ] [ text | alphabetic | ideographic | ideographic-ink ]?
leading-trim: normal | start | end | both
如果你對leading-trim特性感興趣的話,除了閱讀規範之外,還可以閱讀下面幾篇文章:
- The Thing With Leading in CSS
- Leading-Trim: The Future of Digital Typesetting
- Rethinking line-sizing and leading-trim
- 10 ::grammar-error 和 ::spelling-error
::grammar-error和::spelling-error是兩個非常有意思的僞元素選擇器。從字面說我們可以知道, Grammar error 指的是文法錯誤, Spelling error指的是拼寫錯誤。其實這兩種現象在我們平時書寫文本的時候經常可見,可能會由于手誤, 将某個單詞或标點符号用錯,甚至文法上的錯誤。針對于這種現象,我們總是希望有一定的提示資訊來提示我們,比如顔色上的差異,添加一些下劃線等等:
在 CSS Pseudo-Elements Module Level 4 的高亮僞元素中我們可以看到這兩個僞元素的身影:
- ::grammar-error:浏覽器為文法錯誤的文本段添加樣式。
- ::spelling-error:浏覽器為拼寫錯誤的文本段添加樣式。
在CSS中并不是所有屬性都能運用于這兩個僞元素,到目前為止,隻有color、background-color、cursor、text-emphasis-color、text-shadow、outline、text-decoration、fill-color、stroke-color 和stroke-width可以用于這兩個僞元素。
:root::spelling-error {
text-decoration: spelling-error;
}
:root::grammar-error {
text-decoration: grammar-error;
}
[spellcheck]::spelling-error {
text-decoration: wavy underline var(--neon-red);
}
[grammarcheck]::grammar-error {
text-decoration: wavy underline var(--neon-blue);
}
11 新增相對機關:cap、lh、rlh、vi和vb
CSS中機關和值中,機關有:
但在相對機關中并沒有提到cap、lh、rlh、vi和vb這幾個相對機關。
從上表的描述來看,其中cap、lh、rlh的計算都和元素的字型以及行高等有關系。我用下圖來描述一個字型的Cap Height,Line Height等:
三 Web性能
1 contain 和 content-visibility
這兩個屬性是屬于 CSS容器子產品 的,其最大的特點應該是可以幫助Web開發者提高Web頁面的性能:
當容器的内容發生變化時,浏覽器考慮到其他元素可能也會發生變化,于是就會去檢查頁面中所有的元素。一直以來浏覽器都是這麼做的,大家都習以為常了。但從另一方面來說,開發者很清楚目前修改的元素是否獨立、是否影響其他元素。是以如果開發者能把這個資訊通過CSS告訴浏覽器,那麼浏覽器就不需要再去考慮其他元素了,這就是非常完美的事情。而CSS容器子產品中的contain屬性就為我們提供了這種能力。
@Manuel Rego Casasnovas在《An introduction to CSS Containment》文章中提供的一個示例:
假設一個頁面有很多個元素,在這個示例中,我們有10000個這樣的元素:
<div class="item">
<div>Lorem ipsum...</div>
</div>
使用JavaScript的textContent這個API來動态更改div.item > div的内容:
const NUM_ITEMS = 10000;
const NUM_REPETITIONS = 10;
function log(text) {
let log = document.getElementById("log");
log.textContent += text;
}
function changeTargetContent() {
log("Change \"targetInner\" content...");
// Force layout.
document.body.offsetLeft;
let start = window.performance.now();
let targetInner = document.getElementById("targetInner");
targetInner.textContent = targetInner.textContent == "Hello World!" ? "BYE" : "Hello World!";
// Force layout.
document.body.offsetLeft;
let end = window.performance.now();
let time = window.performance.now() - start; log(" Time (ms): " + time + "\n");
return time;
}
function setup() {
for (let i = 0; i < NUM_ITEMS; i++) {
let item = document.createElement("div");
item.classList.add("item");
let inner = document.createElement("div");
inner.style.backgroundColor = "#" + Math.random().toString(16).slice(-6);
inner.textContent = "Lorem ipsum...";
item.appendChild(inner);
wrapper.appendChild(item);
}
}
如果不使用contain,即使更改是在單個元素上,浏覽器在布局上的渲染也會花費大量的時間,因為它會周遊整個DOM樹(在本例中,DOM樹很大,因為它有10000個DOM元素):
在本例中,div的大小是固定的,我們在内部div中更改的内容不會溢出它。是以,我們可以将contain: strict應用到項目上,這樣當項目内部發生變化時,浏覽器就不需要通路其他節點,它可以停止檢查該元素上的内容,并避免到外部去。
CSS容器子產品中的content-visibility屬性會顯著影響第一次下載下傳和第一次渲染的速度。此外,你可以立即與新渲染的内容互動,而無需等待内容的其餘部分加載。該屬性強制使用者代理跳過不在螢幕上的标記和繪制元素。實際上,它的工作方式類似于延遲加載,隻是不加載資源,而是渲染資源。
簡單地說,CSS的content-visibility屬性 可跳過不在螢幕上的内容渲染,包括布局(Layout)和渲染(Paint),直到真正需要布局渲染的時候為止。是以利用它可以使用初始使用者加載速度更快,還能與螢幕上的内容進行更快的互動。
上圖來自于@Una Kravets和@Vladimir Levin的《content-visibility: the new CSS property that boosts your rendering performance》一文。從圖中我們可以獲知,使用content-visibility: auto屬性可使分塊的内容區域的初始加載性能提高7倍。
有關于這方面的介紹,還可以閱讀:
- CSS Containment in Chrome 52
- Helping Browsers Optimize With The CSS Contain Property
- An introduction to CSS Containment
- Let’s Take a Deep Dive Into the CSS Contain Property
- CSS Containment
- content-visibility: the new CSS property that boosts your rendering performance
- Short note on content-visibility: hidden
- Using content-visibility: hidden
- Using content-visibility: auto
2 資料服務
資料服務指的是 Data Saver。啥意思呢?不做解釋,直接用一段代碼來描述:
@media (prefers-reduced-data: reduce) {
header {
background-image: url(/grunge.avif);
}
}
我想大家對于@media (prefers-reduced-data: reduce)應該不會陌生吧。是的,它就是我們所說的CSS媒體查詢。隻不過稍有不同的是,這個媒體查詢是根據使用者在裝置上的設定喜好來做條件判斷。比如上面示例代碼,當使用者在裝置上開啟了“Low Data Mode”(低資料模式),會加載grunge.avif圖像,可以幫助iPhone上的應用程式減少網絡資料的使用:
到目前為止,CSS媒體查詢提供了多個媒體特性,可以以使用者在裝置上的喜好設定做為判斷,比如iOS13+開始,iPhone提供的DarkMode模式(prefers-color-scheme):
比如,使用prefers-reduced-motion媒體查詢用于檢測使用者的系統是否被開啟了動畫減弱功能:
上面提到的這些媒體查詢條件都是在 CSS Media Queries Level 5 子產品中新增的。
除了上面提到的之外,還有一些我們平時很少見的媒體查詢條件,比如:
@media (hover: hoveer) {}
@media (hover: none) and (pointer: coarse) {}
@media (hover: none) and (pointer: fine) {}
@media print and (min-resolution: 300dpi) {}
@media (scan: interlace) {}
@media (update) {}
@media(environment-blending: additive){}
@media (color) {}
3 變量字型
變量字型是一個非常有意思的CSS特性,它也常被稱為“可變字型”,先給大家展示一個Demo[16]:
變量字型的目标是讓網站性能更好,同時給使用者提供了更多選擇和擴充。變量字型是類似矢量圖形,允許為各種字型軸定義不同的值。變量字型設計中一般有五個注冊軸,包括字型、字寬、斜體和光學尺寸。每個注冊軸都有一個對應的四個字母的标記,可以映射到現有的CSS屬性:
除了注冊軸之外,字型設計器還可以包含自定義軸。自定義軸讓可變字型變得更具創造性,因為不限制自定義軸的範圍、定義或數量。與注冊軸類似,自定義軸具有相應的四個字母标記。但是,自定義軸的字母标記必須是大寫的。例如,你定義了一個注冊軸是grade,其對應的字母标記是 GRAD。
比如上面示例效果對應的代碼:
.text {
font-weight: 800;
font-style: italic;
font-variation-settings: "SSTR" 183, "INLN" 648, "TSHR" 460, "TRSB" 312, "TWRM" 638, "SINL" 557, "TOIL" 333, "TINL" 526, "WORM" 523;
transition: font-variation-settings .28s ease;
}
.text:hover {
font-weight: 400;
font-style: normal;
font-variation-settings: "SSTR" 283, "INLN" 248, "TSHR" 160, "TRSB" 112, "TWRM" 338, "SINL" 257, "TOIL" 133, "TINL" 426, "WORM" 223;
}
在Firefox浏覽器中,我們還可以通過開發者工具中“字型”選項提供的相關可變字型注冊軸的值調整:
調整完之後,可以獲得新代碼:
p {
font-size: 60px;
line-height: 37px;
letter-spacing: 0.113em;
font-variation-settings: "SSTR" 450, "INLN" 741, "TSHR" 292, "TRSB" 497, "TWRM" 173, "SINL" 557, "TOIL" 728, "TINL" 526, "WORM" 523, "TFLR" 362, "TRND" 516, "SWRM" 536, "TSLB" 509;
font-weight: 491;
}
對應效果如下:
四 Web可通路性
1 :focus-visible 和 :focus-within
一直以來我很容易把:focus-within和:focus-visible混淆。其實:focus-within和:focus-visible都是CSS選擇器 Level 4中使用者操作類僞類選擇器。早前在《初探CSS 選擇器Level 4》中聊過:focus-within,但沒有聊過:focus-visible。
另外,在《CSS :focus-within》教程中就提到過, :focus-within能非常友善處理擷取焦點狀态。當元素本身或其後代元素獲得焦點時,:focus-within僞類的元素就會有效 。:focus-within僞類選擇器的行為本質上是一種父選擇器行為,子元素的狀态會影響父元素的樣式。由于這種“父選擇器”行為需要借助使用者的行為觸發,屬于“後渲染”,不會與現有的渲染機制互相沖突。
如果上面的介紹讓你感到困惑的話,可以看下面這個Demo[17]。你會發現,當
的後代元素得到焦點時,會有一個放大的效果:
實作上圖的效果代碼非常的簡單:
form:focus-within {
box-shadow: 0px 0.2em 2.5em #c4c4c4;
transform: scale(1.025);
}
對于:focus-visible僞類來說,當元素比對:focus僞類并且用戶端(UA)的啟發式引擎決定焦點應當可見時就會生效。這個選擇器可以有效地根據使用者的輸入方式(滑鼠 vs 鍵盤)展示不同形式的焦點。
簡單點說,按鍵盤tab鍵和滑鼠點選得到的焦點效果不同。比如:
/* 連結得到焦點時的樣式 */
a:focus {
}
/*
* 1. 如果連結有焦點,但是浏覽器通常不會顯示預設的焦點樣式,會覆寫上面的焦點樣式
* 2. 不是按鍵盤`tab`鍵讓連結得到的焦點,比如說滑鼠點選連結
*/
a:focus:not(:focus-visible) {
}
/* 按鍵盤tab鍵讓連結得到焦點的樣式 */
a:focus-visible {
}
來看一個具體的案例。這個示例中分别用滑鼠點選鍊鍊和按鍵盤tab鍵讓連結得到焦點,它的樣式是不一樣的:
五 Web美化
1 Color Level 4 和 Level 5
CSS Color Level 4 和 Level 5 兩個子產品主要是為我們推出了一些顔色使用的新屬性,比如:
- :HWB(白色-白色-黑色的縮寫)是另一種指定顔色的方法,類似于HSL,它描述了一開始的色調,然後是一定程度的白色和黑色混合到基本色調
- 和 :Lab是由一個亮度通道和兩個顔色通道組成的。在Lab顔色空間中,每個顔色用L(亮度)、a(從綠色到紅色的分量)和b(從藍色到黃色的分量)三個數字表示。而Lch分别表示了顔色的亮度、飽和度和色調
- :灰色是完全去飽和的顔色,gray()函數表示法簡化了對這組常見顔色的指定,是以隻需要一個數值參數,用來指定顔色的灰階
- :該函數允許在特定的顔色空間中指定顔色
- :該函數是以CMYK(青色、品紅、黃色和黑色)組合,在該裝置上生成特定的顔色
- :根據使用者作業系統來比對顔色
- color-mix() :該函數接受兩個規範,并在給定的顔色空間中以指定的數量傳回它們混合的結果
- color-contrast() :該函數首先使用一種顔色(通常是背景色),然後使用兩種或兩種以上顔色的清單,它從該清單中選擇亮度對比度最高的顔色到單一顔色
- color-adjust() :該函數接受一個規範,并通過指定的轉換函數在給定的顔色空間中傳回調整該顔色的結果 顔色擴充:根據現有的顔色(在這稱為“原始顔色”)在函數的目标顔色空間中生成顔色,它是、、、、、和的擴充顔色
對于Web開發者來說,最大的感受是文法規則有較大的變化:
來看兩個示例:
// Color Level 4
.colour {
--fn-notation: hsl(2rad 50% 50% / 80%);
--neon-pink: color(display-p3 1 0 1);
--gray: lch(50% 0 0);
--fallback: color(lab 25 25 25, display-p3 1 0 1, #ccc);
}
// Color Level 5
.colour {
--pink: color-mix(red, white);
--halfpink: color(var(--pink) / 50%);
--halfred: rgb(from #f00 / 50%);
--darkred: hsl(from red h s calc(l * .25));
}
這裡再特意提一下display-p3顔色,我們可以配合CSS的媒體查詢@media (dynamic-range: high)一起使用:
@media (dynamic-range: high) {
.neon-red {
--neon-glow: color(display-p3 1 0 0);
}
.neon-pink {
--neon-glow: color(display-p3 1 0 1);
}
.neon-purple {
--neon-glow: color(display-p3 0 0 1);
}
.neon-blue {
--neon-glow: color(display-p3 0 1 1);
}
.neon-green {
--neon-glow: color(display-p3 0 1 0);
}
.neon-yellow {
--neon-glow: color(display-p3 1 1 0);
}
.neon-white {
--neon-glow: color(display-p3 1 1 1);
}
}
注意,Display-P3顔色空間顔色要比sRGB顔色空間中的顔色更鮮豔:
也可以說,Display-P3是sRGB的一個超集,大約要大35%:
Safari 97預覽版本可以檢視到display-p3的效果:
同樣的,在color()函數中使用display-p3指定顔色空間時,也可以和sRGB顔色空間互相轉換,如下圖所示:
下面是@Adam Argyle 在Codepen提供的一個有關于display-p3的示例[18]:
2 ::marker
::marker也是CSS的僞元素,現在被納入到CSS Lists Module Level 3規範中。在該規範中涵蓋了清單和計算數器相關的屬性,比如我們熟悉的list-style-type、list-style-position、list-style、list-item、counter-increment、counter-reset、counter()和counters()等屬性。
在CSS中display設定list-item值之後就會生成一個Markers标記以及控制标記位置和樣式的幾個屬性,而且還定義了計數器(計數器是一種特殊的數值對象),而且該計數器通常用于生成标記(Markers)的預設内容。
一時之間,估計大家對于Markers标記并不熟悉,但對于一個清單所涉及到的相關屬性應該較為熟悉,對于一個CSS List,它可以涵蓋了下圖所涉及到的相關屬性:
事實上,CSS的::marker和僞元素::before(或::after)類似,也可以通過content和attr()一起來控制Marker标記的效果。需要記住,生成個性化Marker标記内容需要做到幾下幾點:
- 非清單項li元素需要顯式的設定display:list-item (内聯清單項需要使用display: inline list-item)。
- 需要顯式設定list-style-type為none。
- 使用content添加内容(也可以通過attr()配合data-*來添加内容)。
比如下面這個小示例:
另外,::marker還沒有得到浏覽器支援之前,一般都是使用CSS的計數器來實作一些帶有個性化的有順序清單,比如下面這樣的效果:
是不是很有意思,有關于::marker僞元素更詳細的介紹,還可以閱讀:《 Custom bullets with CSS ::marker》一文。
3 text-emphasis
先上張圖:
上圖的效果就是使用CSS的text-emphasis實作的。在以往我們要給文本添加一些裝飾效果,除了加粗(font-weight)、傾斜(font-style)、陰影(text-shadow),文本上面或下面添加線條(text-decoration)等之外也沒有别的了(當然,還可以使用其他的CSS實作一些特殊效果)。但要實作上圖的效果還是有一定難度的。不過text-emphasis的出現,這一切變得要簡單地多。
text-emphasis是屬于 CSS Text Decoration Module 規範中的一個特性,在 Level 3中和text-emphasis有關的屬性還有text-emphasis-style和text-emphasis-color,而且text-emphasis是這兩個屬性的簡寫屬性。另外還有一個用來指定标記符位置的屬性text-emphasis-position:
.emphasis {
text-emphasis: triangle rebeccapurple;
text-emphasis-position: under;
}
在 Level 4的規範中還新增了text-emphasis-skip屬性。
具體的效果如下:
4 color-scheme
color-scheme屬性來自于 CSS Color Adjustment Module Level 1。如果你在自己的項目中實作過iOS的DarkMode的效果,你肯定使用過CSS的媒體查詢prefers-color-scheme。
:root {
--color: #fff;
--color-bg: #000;
}
@media (prefers-color-scheme: dark) {
--color: #000;
--color-bg: #fff;
}
body {
color: var(--color);
background-color: var(--color-bg)
}
雖然說,這可以讓使用者根據自己喜好來選擇自己喜歡的皮膚,但這并不能覆寫所有的。這個時候可以使用color-scheme屬性來做一定的補充。該屬性允許使用者通過使用者代理控制自動調整色彩模式,比如暗色模式,對比度調整或特定所需的配色方案。這些值與使用者的首選項進行協商,進而産生影響使用者界面(UI)内容的所選顔色方案,例如表單控制和滾動條的預設顔色,以及CSS系統顔色的使用值。
color-scheme有兩種用法,先來看第一種:
:root {
color-scheme: dark light;
}
在:root元素上,使用color-scheme顔色方案進行渲染,會影響畫布的表面顔色(即全局背景顔色),color屬性的初始值和系統顔色的使用值,還應該影響視窗滾動條顔色。
另外一種使用方式是在标簽上:
<meta name="color-scheme" content="dark light" />
要遵守color-scheme CSS屬性,需要先下載下傳CSS(如果它是通過引用的)并進行解析。為了幫助使用者代理立即用所需的顔色方案渲染頁面背景,還可以在元素中提供一個顔色方案值。
由于meta标記和CSS屬性(如果應用到:root)最終都會導緻相同的行為,是以我更建議通過meta标記指定顔色方案,這樣浏覽器可以更快地采用首選方案。
最後給大家提供@tomayac提供的一個關于color-scheme的Demo[19],下圖是dark和light下相應的效果:
這裡僅僅是簡單的說了一下color-scheme屬性,如果想深入的了解還是需要閱讀一些相關的教程:
- CSS Color Adjustment Module Level 1: color-scheme
- Improved dark mode default styling with the color-scheme CSS property and the corresponding meta tag
- Don’t Forget the color-scheme Property
六 其他
1 & > 和 @nest
很多同學應該使用過像Sass、LESS之類的CSS處理器,這些處理器中有一個特大的特性就是選擇器的嵌套,比如Sass中:
.parent {
& > .child {
color: red;
}
}
.child {
.parent & {
color: blue;
}
}
編譯之後的CSS:
.parent > .child {
color: red;
}
.parent .child {
color: blue;
}
以往隻能在CSS處理器中使用這樣的特性,但将來在CSS中也可以使用這方面的特性,因為現在CSS中新增了一個嵌套子產品,即 CSS Nesting Module。有點類似于CSS自定義屬性(變量)特性一樣,最早也是出現在CSS處理器中,現在原生CSS也支援了這方面的特性。
也就是說,在不久的将來(如果在你的工程建構中配置了postcss-preset-env,現在就可以使用):
article, section {
& p {
color: blue;
}
}
相當于:
:is(article, section) p {
color: blue;
}
也就是:
article p,
section p {
color: blue
}
還可以是& >結合起來使用:
article, section {
& > p {
color: blue;
}
}
article > p,
section > p{
color: blue;
}
再來看另外幾種有效的嵌套方式:
.foo {
color: blue;
& > .bar {
color: red;
}
}
/* 等同于 */
.foo {
color: blue;
}
.foo > .bar {
color: red;
}
.foo {
color: blue;
&.bar {
color: red;
}
}
/* 等同于 */
.foo {
color: blue;
}
.foo.bar {
color: red;
}
.foo, .bar {
color: blue;
& + .baz, &.qux {
color: red;
}
}
/* 等同于 */
.foo, .bar {
color: blue;
}
:is(.foo, .bar) + .baz,
:is(.foo, .bar).qux {
color: red;
}
但是下面這幾種寫法将是無效的:
/* 無效,因為沒有嵌套選擇器 */
.foo {
color: red;
.bar {
color: blue;
}
}
/* 無效,因為&不在第一個複合選擇器中 */
.foo {
color: red;
.bar & {
color:blue;
}
}
/* 無效,因為清單中的第二個選擇器不包含嵌套選擇器 */
.foo {
color: red;
&.bar, .baz {
color: blue;
}
}
還可以結合 @nest 使用。下面這幾種嵌套方式都是有效的:
.foo {
color: red;
@nest & > .bar {
color: blue;
}
}
/* 等同于 */
.foo {
color: red;
}
.foo > .bar {
color: blue;
}
.foo {
color: red;
@nest .parent & {
color: blue;
}
}
/* 等同于 */
.foo {
color: red;
}
.parent .foo {
color: blue;
}
.foo {
color: red;
@nest :not(&) {
color: blue;
}
}
/* 等同于 */
.foo {
color: red;
}
:not(.foo) {
color: blue;
}
但像下面這樣嵌套則是無效的:
/* 無效,因為沒有嵌套選擇器 */
.foo {
color: red;
@nest .bar {
color: blue;
}
}
/* 無效,因為不是清單中的所有選擇器都包含嵌套選擇器 */
.foo {
color: red;
@nest & .bar, .baz {
color: blue;
}
}
注意,如果使用@nest時記得要和&結合在一起使用才有效。
2 @property
在CSS Houdini中,最令人興奮的是給CSS自定義屬性和值的API。這個API通過賦予CSS自定義屬性(通常也稱為CSS變量)語義意義(由文法定義)甚至回退值來增強CSS自定義屬性。
簡單地說,可以使用CSS Houdini的CSS自定義屬性和值的CSS.registerProperty()來注冊一個自定義屬性:
CSS.registerProperty({
name: '--colorPrimary',
syntax: '<color>',
initialValue: 'magenta',
inherits: false
});
這樣一來就可以使用已注冊好的--colorPrimary自定義屬性:
.card {
background-color: var(--colorPrimary); /* magenta */
}
.highlight-card {
--colorPrimary: yellow;
background-color: var(--colorPrimary); /* yellow */
}
.another-card {
--colorPrimary: 23;
background-color: var(--colorPrimary); /* magenta */
}
現在或者将來,我們可以直接使用CSS的@property來注冊一個自定義屬性:
@property --gradient-start {
syntax: "<color>";
initial-value: white;
inherits: false;
}
在CSS中就可以直接像下面這樣使用:
.el {
--gradient-start: white;
background: linear-gradient(var(--gradient-start), black);
transition: --gradient-start 1s;
}
.el:hover {
--gradient-start: red;
}
比如下面這個示例[20](請使用Chrome 85+檢視):
在CSS的世界中,還有另外一套規範是和CSS自定義屬性有關的,那就是 CSS Custom Properties for Cascading Variables Module Level 1。使用--在選擇器塊中聲明自定義屬性,然後使用var()函數引用已聲明的自定義屬性,将其當作CSS屬性的值:
:root {
--color: #f09
}
body {
color: var(--color)
}
到目前為止,CSS自定義屬性(也有同學稱為CSS變量)已經得到了主流浏覽器的使用,而且在一些大型Web應用中可以看到其身影。另外CSS自定義屬性被運用的場景也很多,比如說@Adam Argyle就用CSS自定義屬性模拟了一套緩動函數[21],我們可以用于CSS Animation中:
:root {
--ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
--ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
--ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
--ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
--ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
--easing: var(--ease-in-quad);
}
.animation__target {
animation: ani 5s var(--easing) alternate infinite;
}
// JavaScript
const handlerElement = document.getElementById("easing");
handlerElement.addEventListener("change", function (e) {
document.documentElement.style.setProperty("--easing", e.target.value);
});
檢視效果[22]:
3 ::cue和::cue(selector)
::cue和::cue(selector)對我而言是一個全新的東西,這兩個僞元素是 WebVTT: The Web Video Text Tracks Format 子產品中的。
::cue和::cue(selector)兩個僞元素最大的差别是後者帶參數的僞元素。具體的作用:
- ::cue僞元素(不帶參數)比對元素構造的任何WebVTT節點對象清單,但與背景符号對應的屬性必須應用于WebVTT線索背景框,而不是WebVTT節點對象清單。
- ::cue(selector)是帶有參數的僞元素,必須有一個由CSS選擇器組成的參數。它比對元素構造的WebVTT内部節點對象,該元素也比對給定的CSS選擇器。
在CSS中隻有部分屬性可以運用于::cue和::cue(selector)兩個僞元素,比如color、opacity、visibility、text-decoration、text-shadow、background、outline、font、line-height、white-space、text-combine-upright和ruby-position等。
::cue {
color: white;
background-color: hsl(0 0% 0% / 90%);
}
說實話,沒有完全閱讀 WebVTT: The Web Video Text Tracks Format 子產品所有内容,對其并不完全了解。這裡隻是做一個抛磚引玉的作用,如果你的工作内容和WebVTT相關,那應該對你會有一定的作用;如果你對這方面感興趣的話,可以深挖這方面的知識。
最後向大家推薦幾篇和這個話題相關的文章:
- What's new with CSS?[23]
- CSS News July 2020[24]
- What's New in Web 2020[25]
- The Web in 2020: Extensibility and Interoperability[26]
- Next-generation web styling[27]
相關連結
[1]
https://www.londoncss.dev/[2]
https://github.com/w3c/csswg-drafts/issues/3837[3]
https://tympanus.net/codrops/2015/04/08/motion-blur-effect-svg/[4]
https://www.w3.org/TR/SVG11/filters.html[5]
https://tympanus.net/codrops/2019/01/15/svg-filters-101/[6]
https://codepen.io/michellebarker/pen/povdXRW[7]
https://codepen.io/airen/pen/yJGJEJ[8]
https://codepen.io/argyleink/pen/XWdNYaY[9]
https://github.com/flackr/scroll-timeline[10]
https://mdn-web-dna.s3-us-west-2.amazonaws.com/MDN-Browser-Compatibility-Report-2020.pdf[11]
https://codepen.io/antonjb/pen/rNNgxWV[12]
https://codepen.io/rachelandrew/pen/qBOpjPx[13]
https://web.dev/next-gen-css-2019/[14]
https://codepen.io/rachelandrew/pen/WNrRZaV[15]
https://thecsspodcast.libsyn.com/014-pseudo-elements[16]
https://codepen.io/airen/pen/yLNpQQW[17]
https://codepen.io/ericwbailey/pen/KQOpRM[18]
https://codepen.io/argyleink/pen/MWymQGj[19]
https://color-scheme-demo.glitch.me/[20]
https://codepen.io/airen/pen/poyqBGe[21]
https://codepen.io/argyleink/full/BajvPLz[22]
https://codepen.io/airen/pen/ExPryme[23]
https://london-css-2020.netlify.app/[24]
https://www.smashingmagazine.com/2020/07/css-news-july-2020/[25]
https://speakerdeck.com/limhenry/whats-new-in-web-2020[26]
https://css-tricks.com/the-web-in-2020-extensibility-and-interoperability/[27]