別用圖片了,CSS遮罩合成實現帶圓角的環形loading動畫

XboxYan發表於2023-04-04
歡迎關注我的公眾號:前端偵探

今天來用 CSS 實現一個帶圓角的環形 loading 動畫,效果是這樣的

Kapture 2023-03-31 at 11.24.36.gif

先不考慮動畫,其實就是這樣一個圖形

image.png

那麼,如何來繪製呢?下面花兩分鐘一起看看吧

一、CSS實現思路

首先,看到這環形逐漸消失的效果,也就是透明度漸變的效果,肯定要聯想到錐形漸變

conic-gradient() - CSS:層疊樣式表 | MDN (mozilla.org)

透過錐形漸變,可以很輕鬆的實現這樣一個效果,透明到純色的漸變

loading{
  background: conic-gradient(transparent 10%, royalblue 90%)
}

效果如下

image-20230403143119138

然後,整體是一個環形,可以透過徑向漸變配合mask遮罩實現

radial-gradient() - CSS:層疊樣式表 | MDN (mozilla.org)

mask - CSS: Cascading Style Sheets | MDN (mozilla.org)

loading{
  /*...*/
  -webkit-mask: radial-gradient( closest-side circle, transparent 50%, red 51% 99%, transparent 100%);
}

原理是這樣的

image.png

還有一個圓角,可以直接用徑向漸變實現

loading{
  background: radial-gradient( closest-side circle, royalblue 99%, transparent 100%) center top/25% 25% no-repeat,
  conic-gradient(transparent 10%, royalblue 90%);;
}

其實就是兩個相同顏色的漸變疊加到一起形成的,如下

image.png

所以完整程式碼就是

loading{
    width: 200px;
    height: 200px;
    background: 
    radial-gradient( closest-side circle, royalblue 99%, transparent 100%) center top/25% 25% no-repeat,
    conic-gradient(transparent 10%, royalblue 90%);
    -webkit-mask: radial-gradient( closest-side circle, transparent 50%, red 51% 99%, transparent 100%);
}

二、更好地自定義顏色

上面的實現雖然很好的滿足了需求,但是,還是有些CSS設計問題。

比如,我如果需要改變 loading 的顏色,需要改變兩個地方

image.png

很明顯,這樣的實現不太符合 DRY(Don't Repeat Yourself)原則。

有一個比較簡單思路可以用 CSS 變數來傳遞

loading{
    --color: royalblue;
    background: 
    radial-gradient( closest-side circle, var(--color) 99%, transparent 100%) center top/25% 25% no-repeat,
    conic-gradient(transparent 10%, var(--color) 90%);
    -webkit-mask: radial-gradient( closest-side circle, transparent 50%, red 51% 99%, transparent 100%);
}

這樣每次都只需要改變一個變數就行了。

loading.red{
  --color: red;
}

image.png

除了這種方式以外,其實還有一點需要考慮,為啥背景不能幹淨一點、純粹一點呢?換個說法,現在的背景實現對於不瞭解的同學來講,可能會很費勁,能否將這些細節隱藏起來,更直觀地去自定義顏色呢?比如像這種方式

loading.red{
  background: red;
}

如果要實現這樣的效果,就需要將繪製部分全部在mask遮罩中完成,背景只是展示而已。

那麼,如何透過mask遮罩實現這樣的圖形呢?

三、更直觀地去自定義顏色

mask遮罩其實也和 CSS 背景差不多,只是多了一些圖形合成操作,其實就是布林運算,也就是mask-composite

mask-composite - CSS: Cascading Style Sheets | MDN (mozilla.org)
/* Keyword values */
mask-composite: add; /* 疊加(預設) */
mask-composite: subtract; /* 減去,排除掉上層的區域 */
mask-composite: intersect; /* 相交,只顯示重合的地方 */
mask-composite: exclude; /* 排除,只顯示不重合的地方 */

相信在很多圖形設計軟體中都見到類似的操作(下面是 photoshop)

image.png

這個屬性的值(標準和非標準)非常多,-webkit-mask-composite 與標準下的值有所不同,屬性值非常多,如下(chorme 、safari 支援)

-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; /*只顯示不重合的地方*/

之前在這篇文章中有詳細介紹 mask-composite 的用法,有興趣的可以回顧一下

CSS mask 實現滑鼠跟隨鏤空效果

回到這裡,思考一下?,怎麼來繪製這樣一個圖形?

image.png

形狀是一樣的,只是和前面的步驟稍微有些差異

首先還是繪製環形漸變,可以先繪製錐形漸變和環形漸變,如下

loading{
    background: royalblue;
    -webkit-mask: 
    radial-gradient( closest-side circle, transparent 49%, red 50% 99%, transparent 100%),
    conic-gradient(transparent 10%, royalblue 90%);
}

但是這樣兩個漸變會疊加在一起

image.png

其實我們需要是隻顯示兩者重疊的部分,也就是交叉區域,這個特性在mask-composite中對應的就是destination-in或者source-in

loading{
  ...
  -webkit-mask-composite: source-in;
}

效果如下

Kapture 2023-04-03 at 15.55.04.gif

然後是那個圓角,和上面繪製一樣

loading{
  ...
  -webkit-mask: 
    radial-gradient( closest-side circle, royalblue 99%, transparent 100%) center top/25% 25% no-repeat,
    radial-gradient( closest-side circle, transparent 49%, red 50% 99%, transparent 100%),
    conic-gradient(transparent 10%, royalblue 90%)
    ;
  -webkit-mask-composite: source-in;
}

如果直接這樣疊加,會變成這樣

image.png

其實這是因為source-in導致的,三個圖形,最後只顯示了三者重疊的區域。但是我們現在需要的是最上面的圓角直接疊加就行了,不需要裁剪,可以用到source-over

loading{
  -webkit-mask-composite: source-over, source-in;
}

效果如下

Kapture 2023-04-03 at 16.03.31.gif

下面是完整程式碼

loading{
    width: 200px;
    height: 200px;
    background: royalblue;
    -webkit-mask: 
    radial-gradient( closest-side circle, royalblue 99%, transparent 100%) center top/25% 25% no-repeat,
    radial-gradient( closest-side circle, transparent 49%, red 50% 99%, transparent 100%),
    conic-gradient(transparent 10%, royalblue 90%);
    -webkit-mask-composite: source-over, source-in;
}

如果要換顏色,直接更換背景就可以了,還可以是漸變

loading{
  background: conic-gradient(red, orange, red)
}

自定義顏色起來是不是更加直觀?

image.png

四、動畫和安利

動畫很簡單,就是一個無限旋轉的線性動畫,這個沒什麼好說的

loading{
  animation: rotate 1s linear infinite;
}
@keyframes rotate{
  to{
      transform: rotate(360deg); 
  }
}

這樣就實現了文章開頭效果

Kapture 2023-03-31 at 11.24.36.gif

關於線上 demo,這裡安利一下我開發的xy-ui元件庫(目前正在重構中...),裡面 loading 元件就用到了這個實現

https://xy-ui.codelabo.cn

image.png

很多有趣的 CSS 小技巧都可以在這個元件庫中找到,歡迎 star & fork ??????

image.png

五、總結和說明

以上就是本文的全部內容了,稍顯囉嗦,不過也是為了提供更多的思路,下面總結一下實現重點

  1. 整個實現其實用到了錐形漸變和徑向漸變
  2. 正常思路是背景繪製出透明錐形漸變,然後透過 mask 遮罩裁剪出環形
  3. 不過這種思路改顏色稍微麻煩一點,可以透過 CSS 變數傳遞,簡化程式碼
  4. 顏色在背景中不夠直觀,可以考慮將實現細節放到 mask 中
  5. mask遮罩合成可以實現圖形的合成與裁剪,可以更靈活的布林運算
  6. 推薦一下我的元件庫 xy-ui,可以學到更多有趣的 CSS 小技巧

最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤,同時也歡迎喜歡 CSS 的各位關注我,加我微信XboxYan,一起交流,共同進步。

歡迎關注我的公眾號:前端偵探

相關文章