于2022年的CSS新特性,自己之前也有篇原創,CSS 的未來:Cascade Layers (CSS @layer),專門是在介紹@layer(級聯層)的文章,而新的一年會有更多的CSS的新特性會出現在瀏覽器中。
今天分享一篇大漠老師的文章,主要內容是2022年哪些CSS特性將在瀏覽器中出現,以下是正文。
原文: https://www.w3cplus.com/css/what-is-new-css-in-2022.html
作者:大漠老師,請聯系作者獲得授權,非商業轉載請注明出處。
雖然近些年 CSS 變化很快,但我認為 2021 年是 CSS 的元年。在即將過去的 2021 年,CSS 變化非常地大,其中新增了很多特性,比如 CSS 容器查詢、CSS 父選擇器、CSS 層疊控制規則、CSS 子網格等等。而且這些特性已經在個別,甚至是在大部分主流瀏覽器已經可以看到了。幾大主流瀏覽器(Chrome、Firefox、Safari和Edge)還專注于修復瀏覽器兼容性痛點,讓 Web 開發者的工作變得更輕松。
瀏覽器兼容性方面的修復,在 Web.dev 上發布了 2021 年的年中和年終報告,報告雖然簡單,但我們可以看到瀏覽器廠商都在致于力磨平 CSS 特性在瀏覽器上的兼容性。為此,我們應該感謝他們的付出!
隨著 2021 年的結束,讓我們一起來看看 2022 年,我們可以期待哪些 CSS 特性將會在瀏覽器中出現。這了是我連續第三年整理有關于 CSS 新特性方面的文章了,另外兩篇是(可自行搜索):
你即將看到是 2022 年的,你會發現這三篇文章提到的 CSS 特性有相同的,但他們也是有變化的。
@Bramus 前兩天在Twitter 上發了一條信息,他整理了一份 2022 年的將會出現在瀏覽器的 CSS 清單:
有關于清單中列出的 CSS 相關介紹,可以閱讀他的文章:《CSS In 2022》
這份清單中列出的 CSS 特性和 @Adam Argyle 分別在 2020 年 和 2021 年分享的 CSS 新特性列的出的清單差不多,不同的是,在2021年,有些CSS特性已經得到了瀏覽器的支持,有些已經進入了瀏覽器的實驗屬性列表清單,說不定2022年,你就能看到了!
有關于 @Adam Argyle 在 2020 和 2021 分享的 CSS 新特性,我也曾整理過一份,清單有些類似,但內容還是有差異,主要涵蓋了一些我自己在 CSS 方面的認識和嘗試,分別記錄在《2020年CSS有哪些新特性》和《2021年你可能不知道的 CSS 特性》以及《應用于下一代Web的樣式》
當然,如果你也是 CSS 的忠實愛好者,不斷的在關注和推進 CSS 向前發展,那么你可能已經知道了,自 2019 年開始,每年年低都會有一份關于 CSS 發展狀態相關的報告(2019、2020 和 2021)呈現給你。在每年的報告中,我們都可以看到一項關于”開發者期待的CSS特性“的統計:
在第一份報告出來的時候,我特意還花了一點時間去理解這份有關于 CSS的報告:《從9102年的CSS狀態報告中看CSS特性的使用》,另外有幸運在 2020年年底和一些大學生一起聊了一個關于CSS狀態和學習的話題!如果你對該話題感興趣的話,可以閱讀《CSS現狀和如何學習》,文章中附有分享時錄制的視頻!
除此之外,W3C 的 CSS 工作小組,這兩年也都出了 CSS 規范的重點報告,最新的是 2020年 和 2021年的。注意,W3C 關于 CSS 規范的重點報告并不是每年都有的!接下來,我將按著 @Bramus 的 《CSS In 2022》大綱往下梳理,其中會加入一些我自己對這方面的認識和見解。
我需要額外提出的是,接下來內容中提到的 CSS 特性只會涉及到那些全新的或仍然沒有得到瀏覽器支持的(哪怕是實驗性方面的)特性。但這并不代表著它們不值得期待,說不定在2022年你就能使用了!如果你對這方面感興趣的話,請繼續往下閱讀!
在這節中提到的 CSS 特性是近些年來,Web 開發者一直期待的特性(或者說 CSS 中遺失的特性)。這些特性將在 2022 年的某個時候就得到了主流瀏覽器的支持。慶幸的是,有些特性已經在一個或多個主流瀏覽器中得到了支持,其他特性也將隨著時間的推移也會隨之而來。在 2022 年,學習或了解這些 CSS 特性,我想你會獲得收益的!
有意思的是,CSSWG一直還維護著一份關于 CSS 設計中不完整的錯語列表。感興趣的可以點擊這里閱讀!
CSS 容器查詢的特性一直以來都是 Web 設計師和開發者所期待的功能之一,從這幾年的 CSS 發展狀態的報告就可以看得出來。在沒有容器查詢之前,Web 開發者大多是依賴于 JavaScript 腳本來做判斷,從而改變 UI 網格。雖然 CSS 容器查詢存在于 CSS Containment Module 中有些年頭,但一直未得到瀏覽器的支持。慶幸的是,在 2021 年,該特性得到了飛速的發展,時至今日,能在主流瀏覽器中看到 CSS 容器查詢功能。這一切,都離不開 @TerribleMia ,是她為我們帶來了這么好的 CSS 特性,并且一直在推進該特性向前發展。
@TerribleMia 除了設計 CSS 容器查詢特性之外,還設計了 CSS 另外兩個非常優秀的特性,那就是 CSS 的 @layer 和 @scoped 兩個 @ (At rule)規則。詳細的可以閱讀《Miriam's CSS Sandbox》。
CSS 容器查詢 @container 有點類似于 CSS 的媒體查詢 @media ,只是它將根據元素的父容器(或祖先元素)的尺寸(size)或樣式(style)來調整自己或自己后代元素的樣式規則。在沒有 CSS 容器查詢,Web 開發者為了能在不同容器下調整 UI,大多都是依賴于媒體查詢來做。也就是說,有了該特性之后,不需要再依賴視窗大小加添加類名的方式來調整UI了:
上圖演示了,基于視窗的設計和基于容器的尺寸設計。可以說, CSS 容器查詢的出現,除了改變 Web 開發者的開發方式之外,也將給 Web 設計帶來變革命。這個論點在 2021 年的 GDS 大會上,@Una Kravets 在分享的 《The New Responsive: Web design in a compoent-driven world》主題(該主題視頻可以在 YouTube上訪問)中就提出這樣的觀點:
CSS 容器查詢將會成為下一代響應式 Web 設計必不可少的特性之一。
有了 CSS 容器查詢之后,我們就可以基于同一個組件,在不同尺寸下調整其UI,比如我們手淘 APP 頂部的搜索框效果:
上面效果的關鍵性代碼如下:
.form__container {
container: inline-size;
}
.form {
display: grid;
align-items: center;
}
@container (min-width: 480px) {
.form {
grid-template-columns: min-content 1fr 200px;
grid-template-areas: "searchIcon searchInput button";
grid-template-rows: 88px;
gap: 10px;
}
}
@container (min-width: 768px) {
.form {
grid-template-columns: min-content 1fr min-content 200px;
grid-template-areas: "searchIcon searchInput cameraIcon button";
grid-template-rows: 88px;
gap: 10px;
}
}
詳細代碼可以點擊這里閱讀或獲取。
CSS 容器查詢在主流瀏覽器都能體驗效果了,但需要開啟其相關標記。如果你要運用于生產環境,可以考慮其相應的 Polyfill:
CSS容器查詢特性絕對是眾望所歸的一個特性,在2022年更會全速向前!
有關于 CSS 容器查詢更多的介紹可以移步閱讀下面這些文章:
CSS 選擇器是 CSS 中最基礎的知識,也是必不可少的一部分,如果你沒掌握好 CSS 選擇器的話,那么你將會在 CSS 的世界中有那種”眾里尋她千百度,驀然回首,那人卻在燈火闌珊處“的感覺!也正因此,CSS 選擇器模塊的迭代非常的快,現在已經進入 Level 4 版本了。在 Level 4 版本中,添加了多個偽類選擇器,比如 :is() 、:where() 、:not() 和 :has() 等,而其中 :has() 選擇器也是我們期待已久的一個選擇器。@SaraSoueidan 在 Twitter 上引用 @Jen Simmons 的話說:
:has()選擇器本質上就是CSS中期待已久的父選擇器!
CSS 的 :has() 偽類選擇器和 :not() 有點相似,也被稱為結構性偽類選擇器,在 CSS 的函數中也稱之為 動態偽類函數。它允許你更精細地匹配元素:
:has() 偽類代表一個元素,如果作為參數傳遞的任何選擇器至少與一個元素相匹配!
簡單地說,元素只有在傳遞到 :has() 中的選擇器至少匹配一個元素時才會被選中。這樣理解起來似乎有點暈,我們來看個簡單地示例:
figure img {
aspect-ratio: 21 / 9;
border: 5px solid #3f51b5;
}
figure:has(figcaption) img {
border: 5px solid #9c27b0;
}
上面示例中的 figure img 選擇器大家應該都明白,表示選中 <figure> 元素中的所有 <img> 元素;而 figure:has(figcaption) img 選擇器表示的是選中 包含了<figcaption> 元素的 <figure> 元素中的所有 <img> 元素。注意,這里:has() 中傳了個 figcaption 選擇器作為其參數。
<!-- 未匹配,因為 figure 沒有包含 figcaption 元素 -->
<figure>
<img alt="" src="" />
</figure>
<!-- 會匹配,因為 figure 包含了 figcaption 元素 -->
<figure>
<figcaption></figcaption>
<img alt="" src="" />
</figure>
在支持的瀏覽器中你將看到的效果如下:
注意,Safari TP 137 是目前唯一一個默認支持 :has() 選擇器的瀏覽器。
雖然說 :has() 被稱為 CSS 的父選擇器,但它的作用遠不止于此。我們可以使用 :has() 和 :not() 等選擇器相互結合,實現一些更復雜的效果。
就上面示例而言,當你在 <input type="email"> 輸入的值是否有效時,表單不同狀態下有不同的 UI 效果:
是不是很有意思。如果對 CSS 的 :has() 選擇器感興趣的話,還可以閱讀《CSS 選擇器:is() 和 :where() 與 :has() 有什么功能》一文。
更多關于選擇器的內容:
CSS的級聯順序一直以來令眾多開發者(特別是不熟悉CSS的開發者)感到困惑和頭痛。CSS 新的 @ 規則 @layer 將可以讓 CSS 的級聯順序按照你的意圖來進行控制。簡單地說,@layer 可以通過分層的方式,讓你適當控制同源規則的級聯排序。
比如下面這個示例:
/* 預設級聯層的順序,并且相鄰級聯層之間有逗號分隔 */
@layer setting, tool, generic, element, object, component, utilities;
@layer setting {
/* 附加到級聯層 setting 中的 CSS */
}
@layer tool {
/* 附加到級聯層 tool 中的 CSS */
}
@layer generic {
/* 附加到級聯層 generic 中的 CSS */
}
@layer element {
/* 附加到級聯層 element 中的 CSS */
}
@layer object {
/* 附加到級聯層 object 中的 CSS */
}
@layer component {
/* 附加到級聯層 component 中的 CSS */
}
@layer utilities {
/* 附加到級聯層 utilities 中的 CSS */
}
上面只是演示了 @layer 規則的一個基本使用,而與 @layer 相關的知識和細節很多,這里就不深入展開了。要是你對該特性感興趣的話,可以閱讀《初探 CSS 的級聯層(@layer)》一文。到目前為止,你們可以在 Chromium 99、Firefox 97 和 Safari TP 133 中查閱。注意,Safari 是最早支持該特性的瀏覽器。
有關于 CSS 中層疊更多的話題還可以閱讀:
在 CSS Color Module Level 5 中新增了兩個處理顏色的新函數,即 color-mix() 和 color-contrast() ,除此之外,還擴展了以前的顏色函數(比如 rgb() 、hsl() 、hwb() 、lab() 和 lch() 等)功能,可以在一個顏色的基礎上改變某一個或某幾個參數的值,從而得到一個新的顏色。比如上圖部的 hsl() 函數,我們可以基于 --theme-primary 顏色(假設它的值為 hsl(274, 61%, 50%))改變其飽和度(從 50% 調整到 30% )從而得到一個新的顏色 hsl(274, 61%, 30%) 。這個特性,可以讓我們在很容易的在 CSS 控制品牌色的色盤:
說個題外話,我最近的主要工作內容之一是將 Design Token 運用于前端生產的工程鏈路中,并且根據組件可變參數來自動生成不同風格的 UI 組件。那么,在未來的某一天,我就可以使用這種特性來為品牌色構建色盤。
新增的 color-mix() 和 color-contrast() 函數相對來說要更復雜一些。其中 color-mix() 函數有點類似于設計師調色一樣:允許你在一個給定的顏色空間中混合兩種顏色。
比如:
:root {
--theme-color: #ff0000;
}
.text-primary-dark {
color: color-mix(var(--theme-primary), black 10%);
}
.text-primary-darker {
color: color-mix(var(--theme-primary), black 20%);
}
color-contrast() 函數比較有意思,特別是在用于構建可訪問性 Web 的時候特別有用。因為它可以幫助我們提高 Web 可訪問性方面的能力(更好的控制文本色和背景色的對比度)。其主要作用是獲取一個顏色值,并將其與其他顏色的列表進行比較,從列表中選擇對比度最高的一個。
比如color-contrast(white vs red, white, green) ,分別會拿red 、white 和 green 與 white 進行對比,其中 green 和 white 對比度最高,最終會取 green 顏色:
你也還可以像這樣使用:color-contrast(wheat vs tan, sienna, #d2691e to AA-large) 。它會將 wheat 與 tan 、sienna 和 #d2691e 進行對比,最終 sienna 顏色獲勝,因為它與 wheat 顏色的對比度為 4.273 ,超過了 AA-large 的閾值。
Safari TP 124 已將 hwb()、lch() 、lab() 、color-mix() 和 color-contrast() 置為默認功能!
額外提一下,其中 hwb() 、lch() 和 lab() 所表達的顏色空間和 rgb() 還是有差異的,它們能將顏色表達的更細膩。就拿 hwb() 函數來說:
基于同一色相 0deg 描述的顏色:
有關于 CSS 顏色更多介紹,可以閱讀:
”視窗單位“對于大家來說應該不會感到陌生了,特別是針對移動端開發的同學來說。因為移動端現在主流的適配方案之一就是采用視窗單位來處理的。但很多同學所知道的視窗單位應該只是 vw 、vh 、vmin 和 vmax :
視窗單位給移動端開發的適配是帶來了極大的優勢,但我想你在使用視窗單位的時候,應該也碰到了iOS上 Safari 的兼容性問題。因為,在iOS上的 Safari 有一個長期存在的,極其惱人的Bug,它不能與 vh 單位很好的配合。如果你將一個容器的高度設置為 100vh 時,會導致這個元素有點太高(會出現滾動條)。造成這種現象的主要原因是移動端上的 Safari 在計算 100vh 時忽略了它的部分用戶界面。
如果你對 iOS 的 Safari上 100vh 的相關問題以及解決方案感興趣的話,還可以移步閱讀:
如果你不想花過多時間搞清楚這個問題的話,只是想快速解決這個問題,那可以將下面這段代碼放到你的代碼片段中:
body {
height: 100vh;
}
@supports (-webkit-touch-callout: none) {
body {
height: -webkit-fill-available;
}
}
雖然上面的代碼可以解決 100vh 在 iOS Safari 引起的問題,但終究也只能說是一種 Hack 手段(事實上,CSS 中有很多黑魔法,這里不是主要聊這個的,顠過)。慶幸的是,CSS Values and Units Module Level 4 新增了幾個與視窗有關系的新單位:
也有相應的單位用于 CSS 的邏輯屬性中,比如 svi/svb 。有關于 這幾個新增的視窗單位更詳細的介紹,可以閱讀 @Bramus 的 《The large, Small, and Dynamic Viewports》一文和@Arek Nawo 的 《Investigating the new CSS viewport relative units》一文。既然聊起了 CSS 的單位,那就再多花點篇幅和大家再多聊幾個新增的 CSS 單位,也是蠻有意思的。先說 CSS Values and Units Module Level 4 新增的 lh 和 rlh 吧。
簡單地回憶一下 CSS 中另一對單位:em 和 rem 。稍微了解 CSS 的同學都知道:- em 是相對于元素自已的 font-size 值計算(除元素自身的 font-size 取值為em時,元素自身的 font-size 值單位為 em時,其相對于其父元素的font-size值計算) - rem 是相對于HTML文檔的根元素的 font-size 值計算,文檔的根元素一般是指 <html> 元素
這兩個新增的 lh 和 rlh 與 em 和 rem 非常的相似,只不過他們相對的是 line-height 的值計算:- lh 相對于元素自己的 line-height 計算 - rlh 相對于文檔根元素(<html> )的 line-height 計算
你肯定會問,這樣的單位有何用處呢?我想大家在還原 UI 設計稿的時候,肯定碰到了因為字體不同以及 line-height 值不同,讓元素在視覺上看上去總是對不齊。那么,有了 lh 和 rlh 之后,事情會變得好一點。比如下圖這樣的場景:
以前上圖這樣的標記,常把寬高設置為 1em ,那么現在可以設置 1lh :
::marker {
width: 1lh;
height: 1lh;
}
到目前為止,僅 Safari TP 105 聲稱支持這兩個相對單位 lh 和 rlh 。前面提到 CSS 容器查詢特性一直以來是 Web 設計師和開發者一直期待的特性,并且在 2021 年得到快速發展。同時,和 CSS 容器查詢特性一起出現的 ”*容器查詢單位“ 也是很有意義和作用的。只不過,CSS 容器查詢單位與 CSS 容器查詢模塊放在一起,并未和其他的 CSS 單位納入一起。CSS 容器查詢單位和視窗單位有點類似,不同的是 視窗單位相對于瀏覽器視窗尺寸計算,而容器查詢單位相對于查詢容器計算。使用容器查詢長度單位的樣式表可以更容易將一個組件從一個查詢容器中移到另一個查詢容器。CSS容器查詢的單位主要有:
早其容器查詢的單位定義的是 c*(比如,cw 、ch 、cmin 和 cmax )之類的,但其中有些單位會和 ch 單位產生沖突,因此,最終確定的容器查詢單位是以 cq* 開始的。也就是上面所列的幾個!
正如 @Miriam Suzanne(最初提出建議的人,也是規范的編輯)所分享的,這些 CSS 單位是Chromium中實驗性容器查詢支持的一部分。
更為有趣的是,@Ahmad Shadeed 在他的文章《CSS Container Query Units》一文中提出 qw 、qh 、qmin 和 qmax 以及他們對應的邏輯屬性的單位qi 、qb 。
@Ahmad Shadeed 使用這些新的CSS單位運用在 font-size 上。可以用于容器查詢的一坨 CSS 代碼,在clamp() 比較函數中使用cqw 單位來取代。
早在2018年和大家聊改變滾動體驗的時候就介紹過 overscroll-behavior 屬性,我們可以通過該屬性覆蓋 overscroll 容器(指的是內容寬或高大于容器的寬或高,出現滾動條)時的默認行為。拿一個具體的實例來說,比如我們在構建彈框的時候,彈框內容過高也會出現滾動條,這個時候就會有兩個滾動條出現,一個是彈框的,一個是body 的,其默認行為會像下面錄屏的效果:
彈框無法滾動時,其底部的內容可以繼續滾動。說實話,這樣的默認滾動行為給用戶的體驗是極差的。更多的時候,我們希望彈框無法滾動時,其底部的滾動條也不能滾動。如下所示:
要實現上面視頻的效果,我們就需要使用 overscroll-behavior 屬性,并且將其值設置為 contain :
.modal {
overscroll-behavior-y: contain;
overflow-y: auto;
}
這個特性早在 Firefox 36 和 Chrome 63 就開始得到支持了,只不過Safari還未得到支持,不過 Safari 也在迎頭趕上。
有關于 overscroll-behavior 的相關介紹,可以閱讀 @Ahmad Shadeed 的《Prevent Scroll Chaining With Overscroll Behavior》一文。
在 CSS 中改善滾動體驗,除了overscroll-behavior 屬性之外,還有其他的一些屬性,比如滾動捕捉 scroll-snap (Scroll Snap模塊中的屬性)、pull-to-refresh 等。CSS 除了要以控制滾動行為之外,也可以控制滾動條的樣式。在今年以前,CSS 控制滾動條樣式是使用瀏覽器的一些私有屬性來搞定:
就在 2021 年 12 月 09日,W3C 發布了的 CSS Scrollbars Styling Module Level 1 規范,該規范提供了 scrollbar-color 和 scrollbar-width 兩個屬性,用來給滾動條設置樣式。以后,我們要以使用它們來輕易完成滾動條樣式的定制:
.section { scrollbar-color: #6969dd #e0e0e0; scrollbar-width: thin;}
我想大家都知道,使用 CSS 來美化滾動條樣式主要原因之一是因為滾動條在不同的系統平臺上顯示有差異,外觀不統一。除此之外,還有另一個問題。在 Web 上顯示滾動條有一個副作用,那就是 內容的布局可能會根據滾動條的類型而改變。如果我們想防止由滾動條引起的一些不必要的布局變化,希望有相應的 CSS 屬性來處理。以前沒有,但現在有了。CSS Overflow Module Level 4 新增了一個 scrollbar-gutter 屬性,有了這個屬性,開發者就可以更好的控制滾動條,或者說解決因滾動條類型不同引起布局的差異變化。下圖中展示了 scrollbar-gutter 的取值不同時的效果:
如果你對美化滾動條以及 scrollbar-gutter相關的知識感興趣的話,可以閱讀:
就 Web 布局 而言,雖然 Flexbox 很優秀了,但在二維布局中 Flexbox 還是有很大的局限性。在整個 Web 布局的技術體系中,只有 CSS Grid 才是唯一的二維布局。CSS Grd 布局在這幾年中一直在不斷的向前推進,已經得到了主流瀏覽器的支持。CSS Grid 能這么快向前推進,除了要感謝瀏覽器廠商的開發者團隊之外,我個人認為還必須要感謝 @Jen Simmons 和 @Rachel Andrew。
@Jen Simmons 和 @Rachel Andrew 除了是 CSS Grid 規范的締造者,還是 CSS Grid 布道者,她一直在社區努力推進 Grid 向前發展。
CSS Grid 已經有很多年了,該模塊中的很多特性在主流瀏覽器中都得到了支持。在 2021 年,我自已陸續花了近半年的時間,對 CSS Grid 技術進行了系統的學習,并且整理了二十多篇有關于 CSS Grid 方面的系列教程,在這個系列中除了 CSS Grid 的理論知識之外,還有一些實戰案例。要是你對這方面感興趣的話,可以從《2022年不能再錯過 CSS 網格布局了》一文中索引。這里著重把 CSS 網格中的子網格 subgrid 單獨拿出來是原因的。子網格從定義到今天,經歷了很多個版本的演變,而且在 CSS 社區也引起來很大的爭議。在 CSS Grid 出現之后,就有人提出:
在 CSS 網格布局系統中應該要有一個子網格,即 subgrid
為此,最早定義的 subgrid 像 grid 和 inline-grid 一樣,它只是 display 屬性的一個值:
.subgrid {
display: subgrid;
}
不過沒過多年,subgrid 就從 display 屬性中移除了。使用嵌套網格來模擬子網格:.grid { display: grid; grid-template-columns: repeat(4, 1fr); }
.grid__item { display: grid; grid-template-columns: repeat(4, 1fr);}
簡單地說,在需要嵌套網格的網格項目上再次使用 display: grid 以及 grid 相關的屬性重新定義一個網格。只不過,這種方式也問題存在:很難將嵌套網格項目與父網格對齊。換句話說,父網格和嵌套網格是兩個獨立的網格,他們有著自己獨立的網格參數。為了讓子網格能繼續父網格的相關參數,才又將 subgrid 再次引入到 CSS Grid 系統中,只不過,subgrid 不再是 display 的值,而是網格屬性 grid-template-columns 或 grid-template-rows 屬性的值。
.grid__container { display: grid; grid-template-columns: 1fr 2fr 3fr 2fr 1fr; grid-template-rows: 1fr 2fr 2fr 1fr; gap: 1rem; } .grid__container--subgrid { grid-column: 2 / 5; grid-row: 2 / 4; } .grid__container--subgrid { display: inherit; grid-template-columns: subgrid; grid-template-rows: subgrid; }
正如上圖所示,這就是subgrid的作用:通過設置grid-template-columns或grid-template-rows為subgrid,它將與父網格對齊。真正的子網格可以使用父網格的相關參數。簡單地說,使用該特性,可以輕易實現像下圖這樣的卡片布局:
還有一段時間,也有另一種建議,那就是使用 display: contents 來替代 subgrid 。而到今天為止,在網格布局系統中,嵌套網格、子網格和 display: contents 模擬的子網格都同時存在。他們都有著自己的特性。這里就不花過多時間闡述了。subgrid已成為 CSS Grid 模塊中不可或缺的一個特性,直到今天為止(2012年的最后一天),也僅 Firefox 瀏覽器支持。不過,Chrome 已進入 WIP 階段,我想 2022 年上半年,你就能在 Chrome 瀏覽器體驗 subgrid 的效果。
既然提到 CSS Grid,那就順嘴說一下 CSS 的 gap 屬性吧。該屬性不只是 Grid 布局獨有的,在 CSS Flexbox 、Grid 和多列布局中都有 gap 屬性。在布局中,在某些場景中,給相鄰元素之間設置間距要比 margin 容易地多。
從 @Jen Simmons 發的推特消息中可以獲知,Safari 14.1 開始也支持 Flexbox 中的 gap 屬性。我自測了一下,Grid中的 gap 也得到支持了。也不是說,現在主流瀏覽器都已支持 gap 屬性了:
Web 中有很多控件的 UI 效果是跟隨系統走的。比如表單中的一些控件,比如常用的輸入框(<input>)、單選按鈕,復選框,進度條等。
以往,為了在 UI 的風格上能滿足設計師的需求,即 所以平臺上使用風格一致的UI效果。為此,Web 開發者需要增加額外的開發工作量,采用自定義表單控件的方式來讓 UI 網格統一。為了讓開發者能更好的滿足設計師的需求,并且快速讓各種平臺統一 UI 網格, CSS Basic User Interface Module Level 4 新增了 accent-color 屬性,可以很輕易的控制 Web 控件 (Widget Accent) UI:
:root { accent-color: deeppink; } @media (prefers-color-scheme: dark) { :root { accent-color: hsl(328 100% 65%); }}
既然提到 Web 表單中的控件,那就給大家提兩個有關于 `` 元素的屬性。因為這兩個屬性能給你的用戶帶來更好的體驗,特別是在操作表單的時候。除了給 <input> 元素的 type 指定不同值時,可以提供不同鍵盤類型之外,還可以使用 inputmode 屬性:
inputmode 可以喚起不同類型的軟鍵盤,另一個改善用戶體驗的是給 <input> 元素的 enterkeyhint 屬性設置不同的值,可以改變軟鍵中的 Enter 鍵的類型的操作行為:
上面提到的,不管是 CSS 還是 HTML 的屬性,都是用來改變用戶體驗的。再提一個和 HTML相關的屬性。
自Safari 15 開始,我們在 HTML 的 <meta> 標簽為 theme-color 設置顏色,讓瀏覽器自身的 UI 顏色有讓開發者來控制:
特別聲明,這里的 theme-color 不是 CSS 屬性,他是 HTML 的 <meta> 元素的 name 的一個值,結合 content 和 media 能輕易控制系統級的顏色。
似乎跑題了!我們要聊的是 2022 年的 CSS !嗯!那我們繼續回到 CSS 的世界中來。
CSS 媒體查詢 @media 已不是什么新特性了(除了新增的一些用戶偏好的條件設置)。但今天要提出的是他的語法規則的寫法。先上一張圖吧:
以往在 @media 或者在 @container 規則中寫判斷條件時使用 min-width 和 max-width 較多,不知道大家是否和我一樣有這樣的感覺,使用 min-width 和 max-width 很多時候傻傻分不清楚他們的范圍。為此,我總是喜歡使用下圖來做判斷:
自 CSS Media Queries Level 4 開始,我們可以使用較為熟悉的數學表達式了,在媒體條件中使用 > 、>= 、< 或 <= 等數學表達式:
上圖中使用 @media 語法表達的話,像下面這樣:
/* Old Way */
@media (max-width: 768px) {
…
}
@media (min-width: 375px) {
…
}
@media (min-width: 375px) and (max-width: 768px) {
...
}
/* New Way */
@media (width <= 768px) {
…
}
@media (width >= 375px) {
…
}
@media (375px <= width <= 768px) {
...
}
同樣的,數學比較運算符表達式也同樣可以運用于 @container 容器查詢的條件判斷中。
如果你對媒體查詢方面的知識感興趣的話,還可以閱讀:
F-mods 是 Font Metrics Override Descriptors(字體度量覆蓋描述符)的簡稱。它對應著 CSS Fonts Module Level 4 規范中的 @font-face 部分的第十一小節,即 默認的字體度量覆蓋。簡單地說,在 @font-face 規則中新增了 ascent-override、descent-override、line-gap-override 和 advance-override 等屬性:
@font-face { font-family: 'Arial' src: local('Arial'); --unitsPerEm: 1000; --lineGap: 10; --descender: -237; --ascender: 1041; --advanceWidthMax: 815; ascent-override: calc(var(--ascender) / var(--unitsPerEm) * 100%); descent-override: calc(var(--descender) / var(--unitsPerEm) * 100%); line-gap-override: calc(var(--lineGap) / var(--unitsPerEm) * 100%); advance-override: calc(var(--advanceWidthMax) / var(--unitsPerEm)); }
其中 advance-override 還未進入 W3C 規范,目前還僅處于提案中;而 ascent-override、descent-override 和 line-gap-override 屬性目前已在 Chromium 87+ 和 Firefox 89+ 中實現!
這四個描述符的組合可以讓我們告訴瀏覽器在下載 Web Fonts 之前字符占用多少空間,來覆蓋回退字體(系統字體)的布局,使其與 Web Fonts 相匹配。簡單地說:這四個描述符可以讓你的系統字體更接近 Web Fonts!其中 ascent-override、descent-override 和 line-gap-override 描述符使我們 能夠完全消除垂直布局的偏移,因為這三個描述符都會影響行高。在 CSS Fonts Module Level 5 規范中又為 @font-face 規則添加了幾個新屬性。比如,用來覆蓋字體上標(sup)和下標(sub)的 superscript-position-override、subscript-position-override、superscript-size-override 和 subscript-size-override 描述符。雖然這幾個屬性還沒有得到任何瀏覽器的支持,但對于 Web 開發者而言,這是希望。
除此之外,還新增了 size-adjust 描述符,它允許我們調整字形的比例系數(百分比)。該描述符取代了前面提到的 advance-override描述符。正如 @Addy Osmani在他自己推特上,是這樣描述 size-adjust:
size-adjust 已經得到了 Chromium 92+ 和 Firefox 89+ 的支持。
說到 @font-face 那就有必要提到大家也一直期待的另一個 CSS 特性,那就是 leading-trim:
leading-trim 其主要作用就是用來改變文本布局。
在鉛印刷術中,為了讓鉛字塊(排成行)之間有一定的間距,排字工人會在行與行之間墊一些鉛條,讓閱讀變得更舒服一些。在那個時代,鍋條一般都放置在鉛塊下方。同樣的,在Web排版上也有鉛條的身影,只不過他分面上下兩個部分。在CSS中引入該特性,它的工作原理就像一個文本框剪切刀。去掉文本框上下之間多余的空間(這個空間其實就是鉛條高度的一半):
h1 { text-edge: cap alphabetic; leading-trim: both; }
示例中用到了text-edge 屬性,簡單地用下圖來描述其作用:
開發 Web 時,對于排版方面是較為復雜的,涉及到的CSS知識也較多,如果你感興趣的話,可以閱讀下面這些文章:
這個和前面提到的 CSS 媒體查詢有點類似。不是 CSS 的新特性,在 CSS Transform Level 2 規范中把以前用到 transform 屬性的 rotate() 、scale() 、translate() 函數變成獨立的 CSS 屬性。而且它們都已經成為主流瀏覽器的實驗屬性,能在瀏覽器中體驗他們。
element {
scale: 2;
rotate: 30deg;
translate: -50% -50%;
}
平移(translate)、旋轉(rotate)和縮放(scale)屬性允許開發者獨立地指定簡單的變換,其方式與典型的用戶界面使用方式一致,而不必記住變換中的順序,使transform()、rotate()和scale()的動作保持獨立并在屏幕坐標中發揮作用。它們被應用的順序是,首先是平移,然后是旋轉,然后是縮放,而不是你定義它們的順序。有了獨立的變換屬性,這也意味著我們可以對它們分別進行動畫和過渡。
@keyframes individual {
50% {
translate: 0 50%;
}
75% {
scale: 1;
}
}
element {
transition: rotate 200ms ease-in-out, scale 500ms linear;
}
element:hover {
scale: 2;
rotate: -3deg;
}
有關于這方面更詳細的介紹可以閱讀:
接下來提到的 CSS 特性只是部分瀏覽器的實驗性功能,不會得到大多數瀏覽器的支持,或者可能只有在相應的瀏覽器中開啟了實驗性功能標記才能看到效果。這些特性在 2022 年,甚至是再往后一兩年都有可能得不到所有主流瀏覽器的支持。除了不會得到瀏覽器支持,而且規范都有可能會變更。說不定還會從 CSS 中移除!
上圖看上去很熟,對吧!但它不是 SCSS 中的嵌套,是原生 CSS 中的嵌套。在 CSS 方面這算是一個突破性的補充,也算是繼續 CSS 自定義屬性(CSS 變量)的又補充。以往這樣的規則,只能在 CSS 的處理器中運行。在 CSS 的嵌套模塊中,我們可以使用 & 和 @nest 規則在一個樣式規則塊中嵌套另一個樣式規則塊:
table.colortable { & td { text-align: center; &.c { text-transform: uppercase } &:first-child, &:first-child + td { border: 1px solid black } } & th { text-align: center; background: black; color: white; } @nest footer & { font-size: 0.8em; }}
有關于 CSS 的原生嵌套更多的介紹,可以閱讀《圖解CSS:CSS的嵌套》一文。
CSS Cascading and Inheritance Level 5 中引入了 @layer 規則,將在 Level 6 中會引入另一個規則 @scope 。這是一種將樣式范圍擴大到DOM樹的一種用法。
<!-- HTML -->
<div class="dark-theme">
<a href="#">plum</a>
<div class="light-theme">
<a href="#">also plum???</a>
</div>
</div>
/* 當.light-theme和.dark-theme被嵌套時,你可能不會得到預期的結果 */
.light-theme a {
color: purple;
}
.dark-theme a {
color: plum;
}
/* 通過范圍劃分,我們可以解決這個問題,因為a元素的樣式將由其最近的范圍來確定 */
@scope (.light-scheme) {
a {
color: darkmagenta;
}
}
@scope (.dark-scheme) {
a {
color: plum;
}
}
@when ... @else 是 CSS Conditional Rules Module Level 5 新增的特性,他們可以分開來單獨使用,也可以組合在一起使用。
@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) {
/* A */
} @else supports(caret-color: pink) and supports(background: double-rainbow()) {
/* B */
} @else {
/* C */
}
@Stefan Judis 將這個CSS 特性(@when ... @else)有前面提到的 CSS特性,即 CSS 容器查詢 和 媒體查詢的數學運算表達式組合在一起,重新設計了 @Ahmad Shadeed 的 《Conditional Border Radius In CSS》文章中提到的有條件設置圓角:
@Stefan Judis 重新設計的方案:
有關于有條件設置圓角半徑除了這里提到的方案,其實還有其他的方案,你要是想一探究竟的話,可以閱讀前幾天剛整理的《CSS 中的條件圓角技巧》一文。
這里所說的滾動與動畫指 Scroll-linked Animations 模塊中提供的 @scroll-timeline規則和 animation-timeline 屬性。你可以將 CSS 動畫與一個滾動容器的滾動偏移量關聯起來。當你向上或向下滾動一個容器時,鏈接的動畫將相應的前進或后退。
使用 @scroll-timeline 規則和 animation-timeline 構建類似上面的效果,要以按下面三步來走:
/* (1) Define Keyframes */
@keyframes adjust-progressbar {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
/* (2) Define a ScrollTimeline */
@scroll-timeline scroll-in-document {
source: auto;
orientation: block;
scroll-offsets: 0, 100%;
}
/* (3) Attach the Animation + set the ScrollTimeline as the driver for the Animation */
#progressbar {
animation: 1s linear forwards adjust-progressbar;
animation-timeline: scroll-in-document;
}
有關于這方面更詳細的介紹,可以閱讀 @Bramus 提供的系列教程:
@property 是用來注冊一個變量的,該變量是一個 CSS Houdini 中的變量,但它的使用和 CSS 中的自定義屬性(CSS變量)是一樣的,不同的是注冊方式:
// Chrome 78+
// 需要在 JavaScript腳本中注冊
CSS.registerProperty({
'name': '--custom-property-name',
'syntax': '<color>',
'initialValue': 'black',
'inherits': false
})
// Chrome 85+
// 在CSS文件中注冊
@property --custom-property-name {
'syntax': '<color>',
'initialValue': 'black',
'inherits': false
}
他的最大特色之一就是可以指定已注冊的 CSS 變量的類型、初始值,是否可繼承:
雖然它的使用方式和 CSS 的自定義屬性相似,但它要更強大,特別是在動效方面的使用,能增強 CSS 的動效能力,甚至實現一些以前 CSS 無法實現的動效。比如
@property --milliseconds {
syntax: '<integer>';
initial-value: 0;
inherits: false;
}
.counter {
counter-reset: ms var(--milliseconds);
animation: count 1s steps(100) infinite;
}
@keyframes count {
to {
--milliseconds: 100;
}
}
把它和 CSS Houdini 的 Paint API 結合起來,可做的事情更多:
更多這方向的效果可以在 houdini.how 網站上查閱:
CSS Houdini 中的 @property 和 Paint API 是對現在 CSS 能力的擴展,如果你對這方面感興趣的話,可以閱讀:
瀑布流是 Web 中經典布局之一。到目前為止依舊是 JavaScript 實現的瀑布流布局為主要技術方案,雖然使用 CSS 技術在一定條件之下可以完成瀑布流布局,但其局限性也較大。在 CSS Grid 最新模塊中提供了原生的 CSS 瀑布流布局方案:
.masonry {
display: grid; gap: 20px;
grid: masonry / repeat(auto-fill, minmax(250px, 1fr));
}
但一直以來他還只是 Firefox 中的一個實驗屬性,還沒有看到其他瀏覽器有支持,但我期望在 2022 年的某一天能在主流瀏覽器上都能看到 CSS Grid 實現瀑布流的布局的效果。
上面列出的一些 CSS 新特性(也有新語法),具體有哪些新特性能在 2022 年中出現在瀏覽器中,還不得而知。上面的提到的一些特性其實已經在一個或多個瀏覽器中能體驗了。感興趣的可以自己嘗試一下。另外要特別提出的是,上面列的 CSS 屬性清單來自于社區有同行業專家以及我自己的一些積累和見解,如果有不對之處,歡迎一起探討。如果所列清單有遺漏,也歡迎大家分享出來。最后,希望上面的內容能幫助大家了解 CSS 的一些新東西,打開一些未知領域!謝謝!(^_^)
avaScript包含各種對典型編程思想有用的一些技巧,在實際開發中,我們通常希望減少代碼行數;
將對象的值解構為變量是另一種在傳統點表示法之外讀取其值的方法。
下面的示例比較了用于讀取對象值的經典點表示法和對象解構快捷方式。
const restaurant = {
name: "Classico Italiano",
menu: ["Garlic", "Bread", "Salad", "Pizza", "Pasta"],
openingHours: {
friday: { open: 11, close: 24 },
saturday: { open: 12, close: 23 },
},
};
// Longhand
console.log("value of friday in restaurant:", restaurant.openingHours.friday);
console.log("value of name in restaurant:", restaurant.name);
// Shorthand
const { name, openingHours } = restaurant;
const { openingHours: { friday } } = restaurant;
//we can go further and get the value of open in friday
const { openingHours: { friday: { open } } } = restaurant;
console.log(name, friday, open);
Object.entries() 是 ES8 中引入的一項功能,它允許您將文字對象轉換為鍵/值對數組。
const credits = {
producer: "Open Replay",
editor: "Federico",
assistant: "John",
};
const arr = Object.entries(credits);
console.log(arr);
Output: [
["producer", "Open Replay"],
["editor", "Federico"],
["assistant", "John"],
];
Object.values() 也是 ES8 中引入的新特性,與 Object 具有類似的功能。entries() 但這沒有關鍵部分:
const credits = {
producer: "Open Replay",
editor: "Federico",
assistant: "John",
};
const arr = Object.values(credits);
console.log(arr);
Output: ["Open Replay", "Federico", "John"];
傳統的 JavaScript for 循環語法是 for (let i = 0; i < x; i++) { … }。通過引用迭代器的數組長度,此循環語法可用于遍歷數組。共有三個 for 循環快捷方式,它們提供了用于遍歷數組中的對象的各種方法:
for…of 創建一個循環遍歷內置字符串、數組和類似數組的對象,甚至是 Nodelist
for...in 在記錄屬性名稱和值的字符串時訪問數組的索引和對象文字上的鍵。
Array.forEach 使用回調函數對數組元素及其索引執行操作
const arr = ["Yes", "No", "Maybe"];
// Longhand
for (let i = 0; i < arr.length; i++) {
console.log("Here is item: ", arr[i]);
}
// Shorthand
for (let str of arr) {
console.log("Here is item: ", str);
}
arr.forEach((str) => {
console.log("Here is item: ", str);
});
for (let index in arr) {
console.log(`Item at index ${index} is ${arr[index]}`);
}
// For object literals
const obj = { a: 1, b: 3, c: 5 };
for (let key in obj) {
console.log(`Value at key ${key} is ${obj[key]}`);
}
在 JavaScript 中定義對象字面量讓生活變得更輕松。ES6 在為對象賦予屬性方面提供了更多的簡單性。如果變量名和對象鍵相同,則可以使用速記符號。
通過在對象字面量中聲明變量,您可以在 JavaScript 中快速將屬性分配給對象。為此,必須使用預期的鍵命名變量。這通常在您已經有一個與對象屬性同名的變量時使用。
const a = 1;
const b = 2;
const c = 3;
// Longhand
const obj = {
a: a,
b: b,
c: c,
};
// Shorthand
const obj = { a, b, c };
如果你想要簡單的 JavaScript 并且不想依賴第三方庫,這個小技巧很有用。
// Longhand:
const fruits = ['mango', 'peach', 'banana'];
for (let i = 0; i < fruits.length; i++) { /* something */ }
// Shorthand:
for (let fruit of fruits) { /* something */ }
如果您想訪問文字對象中的鍵,這也適用:
const obj = { continent: "Africa", country: "Ghana", city: "Accra" };
for (let key in obj) console.log(key); // output: continent, country, city1.2.
function logArrayElements(element, index, array) {
console.log("a[" + index + "] = " + element);
}
[2, 4, 6].forEach(logArrayElements);
// a[0] = 2
// a[1] = 4
// a[2] = 61.2.3.4.5.6.7.
ES6 的一個有趣的新增功能是解構賦值。解構是一種 JavaScript 表達式,可以將數組值或對象屬性分離到單獨的變量中。換句話說,我們可以通過從數組和對象中提取數據來將數據分配給變量。
const arr = [2, 3, 4];
// Longhand
const a = arr[0];
const b = arr[1];
const c = arr[2];
// Shorthand
const [a, b, c] = arr;
得益于 ES6 中引入的展開運算符,JavaScript 代碼使用起來更加高效和愉快。它可以代替特定的數組函數。展開運算符中有一系列的三個點,我們可以用它來連接和克隆數組。
const odd = [3, 5, 7];
const arr = [1, 2, 3, 4];
// Longhand
const nums = [2, 4, 6].concat(odd);
const arr2 = arr.slice();
// Shorthand
const nums = [2, 4, 6, ...odd];
const arr2 = [...arr];
與 concat() 函數不同,您可以使用擴展運算符將一個數組插入另一個數組中的任意位置。
const odd = [3, 5, 7];
const nums = [2, ...odd, 4, 6]; // 2 3 5 7 4 61.2.
如果您曾經發現自己需要在代碼中編寫多行字符串,那么您可以這樣寫:
// Longhand
const lorem =
"Lorem, ipsum dolor sit amet" +
"consectetur adipisicing elit." +
" Quod eaque sint voluptatem aspernatur provident" +
"facere a dolorem consectetur illo reiciendis autem" +
"culpa eos itaque maxime quis iusto quisquam" +
"deserunt similique, dolores dolor repudiandae!" +
"Eaque, facere? Unde architecto ratione minus eaque" +
"accusamus, accusantium facere, sunt" +
"quia ex dolorem fuga, laboriosam atque.";
但是有一種更簡單的方法,只需使用反引號即可完成。
// Shorthand:
const lorem = `Lorem, ipsum dolor sit amet
consectetur adipisicing elit.
Quod eaque sint voluptatem aspernatur provident
facere a dolorem consectetur illo reiciendis autem
culpa eos itaque maxime quis iusto quisquam
deserunt similique, dolores dolor repudiandae!
Eaque, facere? Unde architecto ratione minus eaque
accusamus, accusantium facere, sunt
quia ex dolorem fuga, laboriosam atque.`;
您的代碼可能偶爾會收到必須以數字格式處理的字符串格式的數據。這不是一個大問題;我們可以快速轉換它。
// Longhand
const num1 = parseInt('1000');
const num2 = parseFloat('1000.01')
// Shorthand
const num1 = +'1000'; //converts to int datatype
const num2 = +'1000.01'; //converts to float data type1.2.3.4.5.6.
我們可以在不使用 (+) 的情況下將許多變量附加到字符串中。模板文字在這里派上用場。用反引號包裹你的字符串,用 ${ } 包裹你的變量,以利用模板文字。
const fullName = "Ama Johnson";
const job = "teacher";
const birthYear = 2000;
const year = 2025;
// Longhand
const Fullstr =
"I am " + fullName + ", a " + (year - birthYear) + " years old " + job + ".";
// Shorthand
const Fullstr = `I am ${fullName}, a ${year - birthYear} years old ${job}. `;
模板字面量為我們省去了許多使用 + 運算符連接字符串的麻煩。
將第一個操作數乘以第二個操作數的冪的結果是求冪運算符 ** 返回的結果。它與 Math.pow 相同,只是 BigInts 也被接受為操作數。
// Longhand
Math.pow(2, 3); //8
Math.pow(2, 2); //4
Math.pow(4, 3); //64
// Shorthand
2 ** 3; //8
2 ** 4; //4
4 ** 3; //641.2.3.4.5.6.7.8.
這個可能以前見過。本質上,這是一種寫整數的奇特方法,沒有最后的零。例如,表達式 1e8 表示 1 后跟八個零。它表示十進制基數 100,000,000,JavaScript 將其解釋為浮點類型。
// Longhand
for (let i = 0; i < 10000000; i++) { /* something */ }
// Shorthand
for (let i = 0; i < 1e7; i++) { /* something */ }
// All the below will evaluate to true
1e0 === 1;
1e1 === 10;
1e2 === 100;
1e3 === 1000;
1e4 === 10000;
1e5 === 100000;
1e6 === 1000000;
1e7 === 10000000;
1e8 === 100000000;
短路評估也可以替代 if...else 子句。當變量的預期值為 false 時,此快捷方式使用邏輯或運算符 || 為變量提供默認值。
let str = "";
let finalStr;
// Longhand
if (str !== null && str !== undefined && str != "") {
finalStr = str;
} else {
finalStr = "default string";
}
// Shorthand
let finalStr = str || "default string"; // 'default string'1.2.3.4.5.6.7.8.9.10.
if 語句用于定義函數參數的默認值。在 ES6 中,您可以在函數聲明本身中定義默認值。如果沒有傳遞任何值或未定義,則默認函數參數允許使用默認值初始化參數。
以前都是在函數體中測試參數值,如果沒有定義就賦值。
默認情況下,JavaScript 中的函數參數是未定義的。然而,設置不同的默認設置通常是有利的。在這里,默認設置可能很有用。
// Longhand
function volume(a, b, c) {
if (b === undefined) b = 3;
if (c === undefined) c = 4;
return a * b * c;
}
// Shorthand
function volume(a, b = 3, c = 4) {
return a * b * c;
}
我們經常使用關鍵字 return 來指示函數的最終輸出。單語句箭頭函數將隱式返回其計算結果(該函數必須省略方括號 {} 才能這樣做)。
對于多行語句,例如表達式,我們可以將返回表達式包裹在括號 () 中。
function capitalize(name) {
return name.toUpperCase();
}
function add(numG, numH) {
return numG + numH;
}
// Shorthand
const capitalize = (name) => name.toUpperCase();
const add = (numG, numH) => numG + numH;
// Shorthand TypeScript (specifying variable type)
const capitalize = (name: string) => name.toUpperCase();
const add = (numG: number, numH: number) => numG + numH;
在函數的開頭聲明變量賦值是個好主意。這種精簡方法可以幫助您在一次創建大量變量時節省大量時間和空間。
// Longhand
let x;
let y;
let z = 3;
// Shorthand
let x, y, z=3;
但是,請注意,出于易讀性考慮,許多人更喜歡用手寫方式聲明變量。
我們可以使用點表示法訪問對象的鍵或值。我們可以通過可選鏈接更進一步并讀取鍵或值,即使我們不確定它們是否存在或是否已設置。如果鍵不存在,則可選鏈接的值未定義。
下面是一個可選鏈的例子:
const restaurant = {
details: {
name: "Classico Italiano",
menu: ["Garlic", "Bread", "Salad", "Pizza"],
},
};
// Longhand
console.log(
"Name ",
restaurant.hasOwnProperty("details") &&
restaurant.details.hasOwnProperty("name") &&
restaurant.details.name
);
// Shorthand
console.log("Name:", restaurant.details?.name);
JavaScript 中的內置 Math 對象通常用于訪問數學函數和常量。一些函數提供了有用的技術,讓我們可以在不引用 Math 對象的情況下調用它們。
例如,我們可以通過使用兩次按位非運算符來獲得整數的 Math.floor() ~~。
const num = 7.5;
// Longhand
const floorNum = Math.floor(num); // 7
// Shorthand
const floorNum = ~~num; // 71.2.3.4.5.
這些技巧,因為它簡潔明了,使編碼變得有趣并且代碼行易于記憶。
小新 編譯自 Insight Data Blog
量子位 出品 | 公眾號 QbitAI
寫個網頁能有多麻煩?在大多數公司里,這項工作分為三步:
1. 產品經理完成用戶調研任務后,列出一系列技術要求;
2. 設計師根據這些要求來設計低保真原型,逐漸修改得到高保真原型和UI設計圖;
3. 工程師將這些設計圖實現為代碼,最終變成用戶使用的產品。
這么多環節,任何地方出一點問題,都會拉長開發周期。因此,不少公司,比如Airbnb已經開始用機器學習來提高這個過程的效率。
△ Airbnb內部的AI工具,從圖紙到代碼一步到位
看起來很美好,但Airbnb還沒公開該模型中端到端訓練的細節,以及手工設計的圖像特征對該模型的貢獻度。這是該公司特有的閉源解決方案專利,可能不會進行公開。
好在,一個叫Ashwin Kumar的程序員創建了一個開源版本,讓開發者/設計師的工作變得更簡單。
以下內容翻譯自他的博客:
理想上,這個模型可以根據網站設計的簡單手繪原型,很快地生成一個可用的HTML網站:
△ SketchCode模型利用手繪線框圖來生成HTML網站
事實上,上面例子就是利用訓練好的模型在測試集上生成的一個實際網站,代碼請訪問:https://github.com/ashnkumar/sketch-code。
目前要解決的問題屬于一種更廣泛的任務,叫做程序綜合(program synthesis),即自動生成工作源代碼。盡管很多程序綜合研究通過自然語言規范或執行追蹤法來生成代碼,但在當前任務中,我會充分利用源圖像,即給出的手繪線框圖來展開工作。
在機器學習中有一個十分熱門的研究領域,稱為圖像標注(image caption),目的是構建一種把圖像和文本連接在一起的模型,特別是用于生成源圖像內容的描述。
△ 圖像標注模型生成源圖像的文本描述
我從一篇pix2code論文和另一個應用這種方法的相關項目中獲得靈感,決定把我的任務按照圖像標注方式來實現,把繪制的網站線框圖作為輸入圖像,并將其相應的HTML代碼作為其輸出內容。
注:上段提到的兩個參考項目分別是
pix2code論文:https://arxiv.org/abs/1705.07962
floydhub教程:https://blog.floydhub.com/turning-design-mockups-into-code-with-deep-learning/?source=techstories.org
確定圖像標注方法后,理想中使用的訓練數據集會包含成千上萬對手繪線框圖和對應的HTML輸出代碼。但是,目前還沒有我想要的相關數據集,我只好為這個任務來創建數據集。
最開始,我嘗試了pix2code論文給出的開源數據集,該數據集由1750張綜合生成網站的截圖及其相應源代碼組成。
△ pix2code數據集中的生成網站圖片和源代碼
這是一個很好的數據集,有幾個有趣的地方:
該數據集中的每個生成網站都包含幾個簡單的輔助程序元素,如按鈕、文本框和DIV對象。盡管這意味著這個模型受限于將這些少數元素作為它的輸出內容,但是這些元素可通過選擇生成網絡來修改和擴展。這種方法應該很容易地推廣到更大的元素詞匯表。
每個樣本的源代碼都是由領域專用語言(DSL)的令牌組成,這是該論文作者為該任務所創建的。每個令牌對應于HTML和CSS的一個片段,且加入編譯器把DSL轉換為運行的HTML代碼。
為了修改我的任務數據集,我要讓網站圖像看起來像手工繪制出的。我嘗試使用Python中的OpenCV庫和PIL庫等工具對每張圖像進行修改,包括灰度轉換和輪廓檢測。
最終,我決定直接修改原始網站的CSS樣式表,通過執行以下操作:
1. 更改頁面上元素的邊框半徑來平滑按鈕和DIV對象的邊緣;
2. 模仿繪制的草圖來調整邊框的粗細,并添加陰影;
3. 將原有字體更改為類似手寫的字體;
最終實現的流程中還增加了一個步驟,通過添加傾斜、移動和旋轉來實現圖像增強,來模擬實際繪制草圖中的變化。
現在,我已經處理好數據集,接下來是構建模型。
我利用了圖像標注中使用的模型架構,該架構由三個主要部分組成:
1. 一種使用卷積神經網絡(CNN)的計算機視覺模型,從源圖像提取圖像特征;
2. 一種包含門控單元GRU的語言模型,對源代碼令牌序列進行編碼;
3. 一個解碼器模型,也屬于GRU單元,把前兩個步驟的輸出作為輸入,并預測序列中的下一個令牌。
△ 以令牌序列為輸入來訓練模型
為了訓練模型,我將源代碼拆分為令牌序列。模型的輸入為單個部分序列及它的源圖像,其標簽是文本中的下一個令牌。該模型使用交叉熵函數作為損失函數,將模型的下個預測令牌與實際的下個令牌進行比較。
在模型從頭開始生成代碼的過程中,該推理方式稍有不同。圖像仍然通過CNN網絡進行處理,但文本處理開始時僅采用一個啟動序列。在每個步驟中,模型對序列中輸出的下個預測令牌將會添加到當前輸入序列,并作為新的輸入序列送到模型中;重復此操作直到模型的預測令牌為,或該過程達到每個文本中令牌數目的預定義值。
當模型生成一組預測令牌后,編譯器就會將DSL令牌轉換為HTML代碼,這些HTML代碼可以在任何瀏覽器中運行。
我決定使用BLEU分數來評估模型。這是機器翻譯任務中常用的一種度量標準,通過在給定相同輸入的情況下,衡量機器生成的文本與人類可能產生內容的近似程度。
實際上,BLEU通過比較生成文本和參考文本的N元序列,以創建修改后的準確版本。它非常適用于這個項目,因為它會影響生成HTML代碼中的實際元素,以及它們之間的相互關系。
最棒的是,我還可以通過檢查生成的網站來比較當前的實際BLEU分數。
△ 觀察BLEU分數
當BLEU分數為1.0時,則說明給定源圖像后該模型能在正確位置設置合適的元素,而較低的BLEU分數這說明模型預測了錯誤元素或是把它們放在相對不合適的位置。我們最終模型在評估數據集上的BLEU分數為0.76。
后來,我還想到,由于該模型只生成當前頁面的框架,即文本的令牌,因此我可以在編譯過程中添加一個定制的CSS層,并立刻得到不同風格的生成網站。
△ 一個手繪圖生成多種風格的網頁
把風格定制和模型生成兩個過程分開,在使用模型時帶來了很多好處:
1.如果想要將SketchCode模型應用到自己公司的產品中,前端工程師可以直接使用該模型,只需更改一個CSS文件來匹配該公司的網頁設計風格;
2. 該模型內置的可擴展性,即通過單一源圖像,模型可以迅速編譯出多種不同的預定義風格,因此用戶可以設想出多種可能的網站風格,并在瀏覽器中瀏覽這些生成網頁。
受到圖像標注研究的啟發,SketchCode模型能夠在幾秒鐘內將手繪網站線框圖轉換為可用的HTML網站。
但是,該模型還存在一些問題,這也是我接下來可能的工作方向:
1. 由于這個模型只使用了16個元素進行訓練,所以它不能預測這些數據以外的令牌。下一步方向可能是使用更多元素來生成更多的網站樣本,包括網站圖片,下拉菜單和窗體,可參考啟動程序組件(https://getbootstrap.com/docs/4.0/components/buttons/)來獲得思路;
2. 在實際網站構建中,存在很多變化。創建一個能更好反映這種變化的訓練集,是提高生成效果的一種好方法,可以通過獲取更多網站的HTML/CSS代碼以及內容截圖來提高;
3. 手繪圖紙也存在很多CSS修改技巧無法捕捉到的變化。解決這個問題的一種好方法是使用生成對抗網絡GAN來創建更逼真的繪制網站圖像。
代碼:https://github.com/ashnkumar/sketch-code
原文:https://blog.insightdatascience.com/automated-front-end-development-using-deep-learning-3169dd086e82
— 完 —
誠摯招聘
量子位正在招募編輯/記者,工作地點在北京中關村。期待有才氣、有熱情的同學加入我們!相關細節,請在量子位公眾號(QbitAI)對話界面,回復“招聘”兩個字。
量子位 QbitAI · 頭條號簽約作者
?'?' ? 追蹤AI技術和產品新動態
*請認真填寫需求信息,我們會在24小時內與您取得聯系。