如果我們的頁面上存在非常多的樣式,譬如有我們開發頁面的時候的自定義樣式,也有引入的組件庫樣式。這時候樣式將會非常混亂難以管理。
當我們想覆蓋一些本身非我們書寫的樣式時候,往往不得不通過使用優先級權重更高的樣式名,去覆蓋那些樣式。
同時,當樣式優先級感到難以控制時,開發者習慣濫用 !important 去解決,這又循環導致了后續更混亂的樣式結構。
基于讓 CSS 得到更好的控制和管理的背景,CSS @layer 應運而生。
CSS Cascade Layers,也叫做CSS級聯層,是Cascading and Inheritance Level5 規范中新增了一個新的 CSS 特性。
@layer聲明了一個 級聯層, 同一層內的規則將級聯在一起, 這給予了開發者對層疊機制的更多控制。語法也非常簡單,看這樣一個例子:
@layer utilities {
/* 創建一個名為 utilities 的級聯層 */
}
這樣,我們就創建一個名為 utilities 的 @layer 級聯層。
@layer規則可以通過三種方式其一來創建級聯層。第一種方法如上方代碼所示,它創建了一個塊級的@規則,其中包含作用于該層內部的CSS規則。
@layer utilities {
.padding-sm {
padding: .5rem;
}
.padding-lg {
padding: .8rem;
}
}
一個級聯層同樣可以通過 @import 來創建,規則存在于被引入的樣式表內:
@import(utilities.css) layer(utilities);
你也可以創建帶命名的級聯層,但不指定任何樣式。例如,單一的命名層:
@layer utilities
或者,多個命名層也可以被同時定義。例如:
@layer theme, layout, utilities
這一做法很有用,因為層最初被指定的順序決定了它是否有父級層。對于聲明而言,如果同一聲明在多個級聯層中被指定,最后一層中的將優先于其他層。
因此,在上面的例子中,如果 theme 層和 utilities 層中存在沖突的規則,那么 utilities 層中的將優先被應用。
即使 utilities 層中規則的 優先級低于 theme 層中的,該規則仍會被應用。一旦級聯層順序建立之后,優先級和出現順序都會被忽略。
這將使創建CSS選擇器變得更加簡單,因為你不需要確保每一個選擇器都有足夠高的優先級來覆蓋其他沖突的規則,你只需要確保它們出現在一個順序更靠后的級聯層中。
注:在已經聲明級聯層的名字后,它們的順序隨即被確立,你可以重復聲明某級聯層的名字來向其添加CSS規則。這些樣式將被附加到該層的末尾,且級聯層之間的順序不會改變。
其他不屬于任何一級聯層的樣式將被集中到同一匿名層,并置于所有層的前部,這意味著任何級聯層內定義的規則都將覆蓋外部聲明的規則。
級聯層允許嵌套,例如:
@layer framework {
@layer layout {
}
}
向 layout 層內部的 framework 層附加規則,只需用 . 連接這兩層。
@layer framework.layout {
p {
margin-block: 1rem;
}
}
如果創建了一個級聯層但并未指定名字,例如:
@layer {
p {
margin-block: 1rem;
}
}
那么則稱為創建了一個匿名層。除創建后無法向其添加規則外,該層和其他命名層功能一致。
@layer [ <layer-name># | <layer-name>? {
<stylesheet>
} ]
級聯層可以通過多種方式聲明:
1、使用@layer 塊規則,并立即為其分配樣式:
@layer reset {
* { /* Poor Man's Reset */
margin: 0;
padding: 0;
}
}
2、使用規則@layer 語句,沒有指定任何樣式:
@layer reset;
3、將@import 與layer關鍵字或layer()函數一起使用
@import(reset.css) layer(reset);
以上每一個都創建了一個名為 的級聯層reset。
級聯層會按它們聲明的順序排序。
在下面的例子中,我們建立四個級聯層:reset,base,theme,和utilities。
@layer reset { /* 創建級聯層 “reset” */
* {
margin: 0;
padding: 0;
}
}
@layer base { /* 創建級聯層 “base” */
…
}
@layer theme { /* 創建級聯層 “theme” */
…
}
@layer utilities { /* 創建級聯層 “utilities” */
…
}
按照它們的聲明順序,層順序變為:
reset
base
theme
utilities
重復使用級聯層名稱時,樣式將附加到現有級聯層。級聯層的順序保持不變,因為只有第一次的出現已經確定順序:
@layer reset { /* 創建第一個級聯層 “reset” */
…
}
@layer base { /* 創建第二個級聯層 “base” */
…
}
@layer theme { /* 創建第三個級聯層 “theme” */
…
}
@layer utilities { /* 創建第四個級聯層 “utilities” */
…
}
@layer base { /* 會將樣式添加至級聯層“base” */
…
}
重新使用級聯層名稱時層順序保持不變的使@layer 語法變得更加方便和嚴謹。使用它,可以預先建立圖層順序,然后將所有 CSS 附加到它:
@layer reset; /* 創建第一個級聯層 “reset” */
@layer base; /* 創建第二個級聯層 “base” */
@layer theme; /* 創建第三個級聯層“theme” */
@layer utilities; /* 創建第四個級聯層 “utilities” */
@layer reset { /* 添加樣式至級聯層 “reset” */
…
}
@layer theme { /* 添加樣式至級聯層 “theme” */
…
}
@layer base { /* 添加樣式至級聯層 “base” */
…
}
@layer theme { /* 添加樣式至級聯層 “theme” */
…
}
當然你可以用更短的語法來聲明級聯層,
@layer reset, base, theme, utilities;
從上面可以看出,多個級聯層被聲明時,最后一個級聯層的聲明會獲勝。像這樣,
@import(reset.css) layer(reset); /* 第一個級聯層 */
@layer base { /* 第二個級聯層 */
form input {
font-size: inherit;
}
}
@layer theme { /*第三個級聯層 */
input {
font-size: 2rem;
}
}
按以往CSS級聯來進行分析的話,form input(多層級)的優先級會大于input,但是由于級聯層所起的作用,@layer theme的input會取勝。
級聯層支持嵌套使用,如下:
@layer base { /* 第一個級聯層*/
p { max-width: 70ch; }
}
@layer framework { /* 第二個級聯層 */
@layer base { /* 第二級聯層的嵌套子級聯層1 */
p { margin-block: 0.75em; }
}
@layer theme { /* 第二級聯層的嵌套子級聯層2 */
p { color: #222; }
}
}
在這個例子中有兩個級聯外層:
base
framework
該framework層本身也包含兩層:
base
theme
如果要將樣式附加到嵌套級聯層,需要使用以下全名來引用它,
@layer framework {
@layer default {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
@layer framework.theme {
/* 這些樣式會被添加到@layer framework層里面的theme層 */
blockquote { color: rebeccapurple; }
}
@media (min-width: 30em) {
@layer layout {
.title { font-size: x-large; }
}
}
@media (prefers-color-scheme: dark) {
@layer theme {
.title { color: white; }
}
}
如果第一個@media (min-width: 30em)匹配(基于視口尺寸),則layout級聯層層將在圖層順序中排在第一位。如果只有@media (prefers-color-scheme: dark)匹配,theme則將是第一層。
如果兩者匹配,則圖層順序將為layout, theme。如果沒有匹配,則不定義層。
實際 Web 開發過程中,總會遇到各種各樣的布局,比如下面的各種“優惠券”,一起來看看吧
首先,碰到這類布局的最佳實現肯定是 mask遮罩 。關于遮罩,可以看一下 CSS3 Mask 安利報告 。這里簡單介紹一下
基本語法很簡單,和 background 的語法基本一致
.content{
-webkit-mask: '遮罩圖片' ;
}
/*完整語法*/
.content{
-webkit-mask: '遮罩圖片' [position] / [size] ;
}
這里的遮罩圖片和背景的使用方式基本一致,可以是PNG圖片、SVG圖片、也可以是漸變繪制的圖片,同時也支持多圖片疊加。
遮罩的原理很簡單,最終效果只顯示不透明的部分,透明部分將不可見,半透明類推
事實上,除了根據透明度(Alpha)來作為遮罩條件,還可以通過亮度(luminance)來決定,比如白色表示隱藏,黑色表示可見。不過目前只有 Firefox 支持
所以,只要能繪制以上各種形狀,就可以實現了。
優惠券大多有一個很明顯的特點,就是內凹圓角。提到圓角,很容易想到 radial-gradient 。這個語法有點復雜,記不住沒關系,可以看看張老師的這篇 10個demo示例學會CSS3 radial-gradient徑向漸變 。
.content{
-webkit-mask: radial-gradient(circle at left center, transparent 20px, red 20px);
}
這樣就繪制了一個半徑為 20px 的透明的圓,不過代碼層面還有很多優化的空間。
進一步簡化就得到了
.content{
-webkit-mask: radial-gradient(circle at 0, #0000 20px, red 0);
}
不錯,又少了好幾個B的流量~ 可以查看在線實例 codepen 優惠券實現1
上面是一個最基本的內凹圓角效果,現在來實現下面幾種布局,比如兩個半圓的,根據上面的例子,再復制一個圓不就可以了?改一下定位的方向
.content{
-webkit-mask: radial-gradient(circle at 0, #0000 20px, red 0), radial-gradient(circle at right, #0000 20px, red 0);
}
這時發現一個圓都沒有了。原因其實很簡單,如下演示,兩層背景相互疊加,導致整塊背景都成了不透明的,所以 mask 效果表現為全部可見。
解決方式有2個,分別是:
.content{
-webkit-mask: radial-gradient(circle at 0, #0000 20px, red 0), radial-gradient(circle at right, #0000 20px, red 0);
-webkit-mask-size: 51%; /*避免出現縫隙*/
-webkit-mask-position: 0, 100%; /*一個居左一個居右*/
-webkit-mask-repeat: no-repeat;
}
動態演示如下,這樣就不會互相覆蓋了
可以查看在線實例 codepen 優惠券實現2
標準屬性下 mask-composite 有 4 個屬性值(Firefox支持)
/* Keyword values */
mask-composite: add; /* 疊加(默認) */
mask-composite: subtract; /* 減去,排除掉上層的區域 */
mask-composite: intersect; /* 相交,只顯示重合的地方 */
mask-composite: exclude; /* 排除,只顯示不重合的地方 */
這個可能有些不好理解,其實可以參考一些圖形軟件的形狀合成操作,比如 photoshop
-webkit-mask-composite 與標準下的值有所不同,屬性值非常多,看下面
-webkit-mask-composite: clear; /*清除,不顯示任何遮罩*/
-webkit-mask-composite: copy; /*只顯示上方遮罩,不顯示下方遮罩*/
-webkit-mask-composite: source-over;
-webkit-mask-composite: source-in; /*只顯示重合的地方*/
-webkit-mask-composite: source-out; /*只顯示上方遮罩,重合的地方不顯示*/
-webkit-mask-composite: source-atop;
-webkit-mask-composite: destination-over;
-webkit-mask-composite: destination-in; /*只顯示重合的地方*/
-webkit-mask-composite: destination-out;/*只顯示下方遮罩,重合的地方不顯示*/
-webkit-mask-composite: destination-atop;
-webkit-mask-composite: xor; /*只顯示不重合的地方*/
是不是一下就懵了?不用慌,可以看到上面有幾個值是 source-*,還有幾個是 destination-*開頭的,source 代表新內容,也就是上面繪制的圖層, destination 代表元內容,也就是下面繪制的圖層(在CSS中,前面的圖層會覆蓋后面的圖層),這里的屬性值其實是借用了Canvas 中的概念,具體可以查看 CanvasRenderingContext2D.globalComposite
記不住沒關系,實際開發可以逐一試驗[捂臉]。具體差異可以查看 codepen -webkit-mask-composite 屬性值演示
了解這個屬性后,上面的疊加問題就很簡單了,設置只顯示重合的地方就行了
.content{
-webkit-mask: radial-gradient(circle at 0, #0000 20px, red 0), radial-gradient(circle at right, #0000 20px, red 0);
-webkit-mask-composite: source-in | destination-in ; /*chrome*/
mask-composite: intersect; /*Firefox*/
}
動態演示如下,這樣只會顯示互相重合的地方
可以查看在線實例 codepen 優惠券實現3
2個圓角的實現了,4個的就很容易了,畫4個圓就行,同樣利用遮罩合成可以輕易實現
content{
-webkit-mask: radial-gradient(circle at 0 0, #0000 20px, red 0), radial-gradient(circle at right 0, #0000 20px, red 0), radial-gradient(circle at 0 100%, #0000 20px, red 0), radial-gradient(circle at right 100%, #0000 20px, red 0); /*4個角落各放一個圓*/
-webkit-mask-composite: source-in | destination-in ; /*chrome*/
mask-composite: intersect; /*Firefox*/
}
可以查看在線實例 codepen 優惠券實現4
上面的例子展示了2個圓角和4個圓角的效果,分別繪制了2個和4個圓,其實這是可以通過平鋪來實現的,只需要一個圓就可以。實現步驟如下
.content{
-webkit-mask: radial-gradient(circle at 20px, #0000 20px, red 0);
}
.content{
-webkit-mask: radial-gradient(circle at 20px, #0000 20px, red 0);
-webkit-mask-position: -20px
}
/*也可以縮寫為*/
.content{
-webkit-mask: radial-gradient(circle at 20px, #0000 20px, red 0) -20px;
}
效果就出來了,是不是很神奇?其實就是利用到了默認的 repeat特性,這里用一張動圖就能明白了
下面紅色邊框內表示視區范圍,也就是最終的效果,這里為了演示,把視線之外的平鋪做了半透明處理,移動表示 position 改變的過程
可以查看在線實例 codepen 優惠券實現5
同樣原理,4個圓角也可以采用這種方式實現
.content{
-webkit-mask: radial-gradient(circle at 20px 20px, #0000 20px, red 0);
-webkit-mask-position: -20px -20px;
}
/*也可以縮寫為*/
.content{
-webkit-mask: radial-gradient(circle at 20px 20px, #0000 20px, red 0) -20px -20px;
}
實現原理演示如下
可以查看在線實例 codepen 優惠券實現6
6個圓角就需要改一下平鋪尺寸了。
.content{
-webkit-mask: radial-gradient(circle at 20px 20px, #0000 20px, red 0);
-webkit-mask-position: -20px -20px;
-webkit-mask-size: 50%;
}
/*也可以縮寫為*/
.content{
-webkit-mask: radial-gradient(circle at 20px 20px, #0000 20px, red 0) -20px -20px / 50%;
}
實現原理演示如下
可以查看在線實例 codepen 優惠券實現7
如果繼續縮小背景圖的尺寸,還可以得到最后的效果
.content{
-webkit-mask: radial-gradient(circle at 10px, #0000 10px, red 0);
-webkit-mask-position: -10px;
-webkit-mask-size: 100% 30px;
}
/*也可以縮寫為*/
.content{
-webkit-mask: radial-gradient(circle at 20px 20px, #0000 20px, red 0) -10px / 100% 30px;
}
實現原理演示如下,其實就平鋪
可以查看在線實例 codepen 優惠券實現8
有些情況下可能單一的一層漸變繪制不了很復雜的圖形,這就需要用到反向鏤空技術了,其實就是上面提到過的遮罩合成 ,這里再運用一下
.content{
-webkit-mask: radial-gradient(circle at 20px 20px, #0000 20px, red 0) -20px -20px / 50%;
}
.content{
-webkit-mask: radial-gradient( circle at 50%, red 5px, #0000 0) 50% 50% / 100% 20px, radial-gradient(circle at 20px 20px, #0000 20px, red 0) -20px -20px / 50%;
-webkit-mask-composite: destination-out;
mask-composite: subtract; /*Firefox*/
}
注意這里用到了 -webkit-mask-composite: destination-out,表示減去,只顯示下方遮罩,重合的地方不顯示
可以查看在線實例 codepen 優惠券實現9
也可以放在兩邊,改一下 position 就可以了
.content{
-webkit-mask: radial-gradient( circle at 5px, red 5px, #0000 0) -5px 50% / 100% 20px, radial-gradient(circle at 20px 20px, #0000 20px, red 0) -20px -20px / 50%;
-webkit-mask-composite: destination-out;
mask-composite: subtract; /*Firefox*/
}
可以查看在線實例 codepen 優惠券實現10
有些同學覺得徑向漸變太復雜,實在是寫不出來,能不能用圖片代替呢?其實也是可行的。這里說的邊框遮罩指的是 mask-border , 目前還在 W3C 草案當中,不過有一個替代屬性 -webkit-mask-box-image
語法和概念和 border-image 非常相似,關于 border-image 可參考這篇文章 border-image 的正確用法,這里主要了解一下用法和效果
.content{
-webkit-mask-box-image: '遮罩圖片' [<top> <right> <bottom> <left> <x-repeat> <y-repeat>]
}
比如有一張這樣的圖片
SVG代碼長這樣,很多工具都可以導出來,實在不會可以直接找設計同學
<svg xmlns="http://www.w3.org/2000/svg" width="60.031" height="60.031" viewBox="0 0 60.031 60.031"><path d="M40 60.027H20.129A20.065 20.065 0 0 0 .065 40H0V20.127h.065A20.066 20.066 0 0 0 20.131.061v-.065H40v.065a20.065 20.065 0 0 0 20.027 20.064V40A20.063 20.063 0 0 0 40 60.027z" fill-rule="evenodd"/></svg>
這里需要轉義一下,可借助張老師的 SVG在線合并工具
.content{
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60.031' height='60.031' viewBox='0 0 60.031 60.031'%3E%3Cpath d='M40 60.027H20.129A20.065 20.065 0 0 0 .065 40H0V20.127h.065A20.066 20.066 0 0 0 20.131.061v-.065H40v.065a20.065 20.065 0 0 0 20.027 20.064V40A20.063 20.063 0 0 0 40 60.027z' fill-rule='evenodd'/%3E%3C/svg%3E") 20;
/*這里的20表示四周保留20像素的固定區域,剩余部分平鋪或者拉伸*/
}
然后就實現了這樣一個形狀,同樣是自適應的
可以查看在線實例 codepen -webkit-mask-box-iamge 實現1
再比如有一張這樣的圖片
.content{
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60.031' height='60.031' viewBox='0 0 60.031 60.031'%3E%3Cpath d='M55.186 30.158a4.965 4.965 0 0 0 4.841 4.959V40A20.063 20.063 0 0 0 40 60.027H20.129A20.065 20.065 0 0 0 .065 40H0v-4.888c.054 0 .1.016.158.016a4.973 4.973 0 1 0 0-9.945c-.054 0-.1.014-.158.015v-5.074h.065A20.066 20.066 0 0 0 20.131.058v-.065H40v.065a20.065 20.065 0 0 0 20.027 20.064v5.07a4.965 4.965 0 0 0-4.841 4.966z' fill-rule='evenodd'/%3E%3C/svg%3E") 20;
}
可以得到這樣一個形狀,兩側的半圓被拉伸了
這時只需要設置平鋪方式 -webkit-mask-box-image-repeat , 這個和 border-image-repeat 是一樣的概念,有以下 4 個值
-webkit-mask-box-image-repeat: stretch; /*拉伸(默認),不會平鋪*/
-webkit-mask-box-image-repeat: repeat; /*重復*/
-webkit-mask-box-image-repeat: round; /*重復,當不能整數次平鋪時,根據情況拉伸。*/
-webkit-mask-box-image-repeat: space; /*重復,當不能整數次平鋪時,會用空白間隙填充*/
幾種平鋪方式的差異如下
這里我們可以采用 round 或者 repeat
.content{
-webkit-mask-box-image: url("...") 20;
-webkit-mask-box-image-repeat: round;
}
可以查看在線實例 codepen -webkit-mask-box-iamge 實現2
以上一共介紹了12種繪制優惠券的案例,應該可以解決掉絕大部分這類布局的問題,這里總結以下幾點
關于兼容性,其實不考慮 IE 都沒有什么大問題,最后的 mask-border 目前只兼容 chrome 內核,移動端可放心使用
感謝閱讀,希望能對日后的工作有所啟發。
用 CSS 最困難的部分之一是處理CSS的權重值,它可以決定到底哪條規則會最終被應用,尤其是如果你想在 Bootstrap 這樣的框架中覆蓋其已有樣式,更加顯得麻煩。不過隨著 CSS 層的引入,這一切都發生了變化。 這個新功能允許您創建自己的自定義 CSS 層,這是有史以來第一次確定所有 CSS 代碼權重的層次結構。 在本文中,我將剖析這對您意味著什么,它是如何工作的,以及您今天如何開始使用它。
什么是層(Layers)
創建您自己的自定義圖層是 CSS 的新功能,但圖層從一開始就存在于 CSS 中。 CSS 中有 3 個不同的層來管理所有樣式的工作方式。
瀏覽器(也稱為用戶代理)樣式 - user agent style
用戶樣式 - User Styles
作者樣式 - Author Styles
瀏覽器樣式是應用于瀏覽器的默認樣式。這就是為什么 Chrome 和 Safari 中的按鈕看起來不同的原因。在瀏覽器層中找到的樣式在瀏覽器之間是不同的,并且給每個瀏覽器一個獨特的外觀。
下一層是用戶樣式,這并不是您真正需要擔心的事情。這些通常是用戶可以編寫并注入瀏覽器的自定義樣式,但瀏覽器不再真正支持這些樣式。用戶可能會更改一些瀏覽器設置,這些設置會向該圖層添加樣式,但在大多數情況下,可以完全忽略該層。
最后,我們來到作者層。這是您最熟悉的層,因為您編寫的每一段 CSS 代碼都屬于這一層。
這些層分開的原因是因為它可以很容易地覆蓋瀏覽器樣式和用戶樣式中定義的代碼,因為層定義了自己的層次結構,完全忽略了權重的影響。
這 3 個 CSS 層是有序的(瀏覽器樣式、用戶樣式、然后是作者樣式),后面層中的每個樣式都將覆蓋前一層的任何樣式。這意味著即使瀏覽器樣式定義了一個超級特定的選擇器,例如#button.btn.super-specific,并且您的作者樣式定義了一個超級通用的選擇器,例如按鈕,您的作者樣式仍然會覆蓋瀏覽器樣式。
這實際上已經是您可能一直在使用而沒有意識到的東西。
* {
box-sizing: border-box;
}
上面的選擇器沒有權重,因為 * 符號對權重沒有貢獻。 這意味著例如使用 p 作為選擇器的 p 標簽的瀏覽器樣式在技術上比 * 選擇器更具體,權重更高。 但是,這一切并不重要,因為作者樣式位于比瀏覽器樣式層晚的層中,因此您的代碼將始終覆蓋瀏覽器樣式。
理解這一點至關重要,因為使用這個新的圖層 API,您可以在作者圖層中創建自己的圖層,從而更輕松地處理特定性。
如何創建你自己的層
下面來看個例子:
很明顯,這是我們正常理解的CSS, ID設置的顏色權重更高,所以按鈕顯示為紅色。讓我們使用@layer給它們加上兩個層,看看是什么效果:
按鈕變成藍色。為什么會這樣?
我們給兩條CSS分別建立了base和utilities層,很明顯,后面創建的層的樣式覆蓋了前面層的樣式,盡管前面層的樣式有更高的權重。這就是層的默認工作原理。當然層的順序是可以指定的,
@layer utilities, base;
@layer utilities, base;
您需要做的就是編寫@layer 關鍵字,后跟以逗號分隔的層列表。 這將按從左到右的順序定義所有層,其中列出的第一層到最后一層的權重是依次增加的。 然后,您可以稍后使用普通的@layer 語法向每個層添加代碼,而不必擔心定義層的順序,因為它們都在這一行中定義。 需要注意的是,這行代碼必須在定義任何層之前出現,所以我通常將它作為我的 CSS 文件中的第一行。如上圖,通過指定層的順序,我們讓base層應用在utilities層之后,所以按鈕又顯示為紅色。
導入層
上面這兩種方式都是導入bootstrap框架的CSS,并且把他們放在framework層中,這樣你如果想要覆蓋它已有的樣式,只需要新建一個自己的層,放置在framework層后面就行。像下面這樣。
匿名層
匿名層不常用,但它寫在后面可以覆蓋其他層的樣式,像下面可以把按鈕設為橙色。
不在層里的樣式
不在層里的樣式會有更高的權重,下面這個列表會讓你看得更清楚覆蓋是怎么發生的
層還可以重疊設置,不過很少用。具體的用法可以查閱相關文檔。
瀏覽器支持
自從IE死了以后,所有主流瀏覽器都已支持這一特性。大家請放心使用。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。