這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 今日,群友提問,如何實現這麼一個 Loading 效果: 這個確實有點意思,但是這是 CSS 能夠完成的? 沒錯,這個效果中的核心氣泡效果,其實藉助 CSS 中的濾鏡,能夠比較輕鬆的實現,就是所需的元素可能多點。參考我們之前的: 使用純 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
今日,群友提問,如何實現這麼一個 Loading 效果:
這個確實有點意思,但是這是 CSS 能夠完成的?
沒錯,這個效果中的核心氣泡效果,其實藉助 CSS 中的濾鏡,能夠比較輕鬆的實現,就是所需的元素可能多點。參考我們之前的:
圓弧的實現
首先,我們可能需要實現這樣一段圓弧:
這裡需要用到的技術是:
角向漸變 conic-gradient()
+ mask
以及兩個偽元素。圖片示意如下:
核心代碼如下圖:
HTML:
<div class="g-container"> <div class="g-circle"></div> </div>
CSS:
:root { --headColor: hsl(130, 75%, 75%); --endColor: hsl(60, 75%, 40%); } .g-container { position: relative; background: #000; } .g-circle { position: relative; width: 300px; height: 300px; border-radius: 50%; background: conic-gradient( var(--headColor) 0, var(--headColor) 10%, hsl(120, 75%, 70%), hsl(110, 75%, 65%), hsl(100, 75%, 60%), hsl(90, 75%, 55%), hsl(80, 75%, 50%), hsl(70, 75%, 45%), var(--endColor) 30%, var(--endColor) 35%, transparent 35% ); mask: radial-gradient(transparent, transparent 119px, #000 120px, #000 120px, #000 100%); &::before, &::after { content: ""; position: absolute; inset: 0; width: 30px; height: 30px; background: var(--headColor); top: 0; left: 135px; border-radius: 50%; } &::after { background: var(--endColor); left: unset; top: 214px; right: 26px; } }
這樣,我們就得到了這樣一個圖形:
氣泡的實現
接下來,我們來實現尾部氣泡向外擴散的效果。
由於這裡涉及了多個氣泡的不同運動動畫,多個標簽元素肯定是少不了了。
因此,接下來我們要做的事情:
- 我們需要多一組元素,將其絕對定位到上述圓環的頭部或者尾部
- 給每個子元素隨機設置多個大小不一的圓,顏色保持一致
- 給每個子元素隨機設置不同方向的,向外擴散的位移動畫
- 給每個子元素隨機設置負的
animation-delay
,造成動畫上的先後順序,並以此形成整個無限迴圈的氣泡擴散動畫
這裡,由於有許多小氣泡的動畫,這個數量,我設置成了 100。那肯定是不能一個一個手寫它們的動畫代碼,需要藉助 SASS/LESS 等預處理器的迴圈、隨機等函數。
核心代碼如下:
HTML:
<div class="g-container"> <div class="g-circle"></div> <ul class="g-bubbles"> <li class="g-bubble"></li> // ... 共 100 個 bubble 元素 <li class="g-bubble"></li> </ul> </div>
CSS:
// 上面圓環的代碼,保持一致,下麵只補充氣泡動畫的代碼 .g-bubbles { position: absolute; width: 30px; height: 30px; border-radius: 50px; top: 100px; left: 235px; background: var(--headColor); } .g-bubble { position: absolute; border-radius: 50%; background-color: inherit; } @for $i from 1 through 100 { .g-bubble:nth-child(#{$i}) { --rotate: calc(#{random(360)} * 1deg); --dis: calc(#{random(100)} * 1px); --width: calc(3px + #{random(25)} * 1px); top: 50%; left: 50%; transform: translate(-50%, -50%); width: var(--width); height: var(--width); animation: move #{(random(1500) + 1500) / 1000}s ease-in-out -#{random(3000) / 1000}s infinite; } } @keyframes move { 0% { transform: translate(-50%, -50%) rotate(0deg); } 75% { opacity: .9; } 100% { transform: rotateZ(var(--rotate)) translate(-50%, var(--dis)); opacity: .4; } }
核心在於 @for $i from 1 through 100 { }
這段 SASS 代碼內部,我們實現了上面說的 (2)(3)(4) 的功能點!
這樣,我們就得到了這樣一個效果,在尾部有大量氣泡動畫,不斷向外擴散的效果:
藉助濾鏡實現粘性氣泡效果
OK,到這裡整個效果基本就做完了。當然,也是剩下最後最重要的一步,需要讓多個氣泡之間產生一種粘性融合的效果。
這個技巧在此前非常多篇文章中,也頻繁提及過,就是利用 filter: contrast()
濾鏡與 filter: blur()
濾鏡。
如果你還不瞭解這個技巧,可以戳我的這篇文章看看:你所不知道的 CSS 濾鏡技巧與細節
簡述下該技巧:
單獨將兩個濾鏡拿出來,它們的作用分別是:
filter: blur()
: 給圖像設置高斯模糊效果。filter: contrast()
: 調整圖像的對比度。
但是,當他們“合體”的時候,產生了奇妙的融合現象。
仔細看兩圓相交的過程,在邊與邊接觸的時候,會產生一種邊界融合的效果,通過對比度濾鏡把高斯模糊的模糊邊緣給幹掉,利用高斯模糊實現融合效果。
基於此,我們再簡單改造下我們的 CSS 代碼,所需要加的代碼量非常少:
- 加上濾鏡 blur() 和 contrast() ,形成融合粘性效果
- 加上整個圓環的旋轉即可效果
- 加上濾鏡 hue-rotate(),實現色彩的變換動畫
.g-container { // ... 保持一致 background: #000; filter: blur(3px) contrast(5); animation: rotate 4s infinite linear; } @keyframes rotate { 100% { transform: rotate(360deg); filter: blur(3px) contrast(5) hue-rotate(360deg); } }
就這樣,我們就大致還原了題圖的效果:
完整的代碼,你可以戳這裡:CodePen Demo -- Pure CSS Loading Animation
修複違和感
當然,上面的效果,乍一看還行,仔細看,違和感很重。
原因在於,擴散出來的小球也跟著半圓環一起進行了旋轉動畫,看上去就有點奇怪。
正確的做法應該是,圓環尾部的氣泡應該是原地發散消失的。
那麼,怎麼能夠做到氣泡效果,一直發生在圓環的尾部,同時消失的時候又不跟著整個圓環一起進行旋轉呢?我們想要的最終效果,應該是這樣:
這裡,我們可以拆解一下。想象,如果去掉圓環的旋轉,其實我們只需要實現這樣一個效果即可:
整個動畫的核心就轉變成瞭如何實現這麼一個效果。看似複雜,其實也很好做。
首先,我們重新改造一下上述的 .g-bubbles
。
- 生成 N 個一樣大小的小球元素,定位在整個容器的中間
<div class="g-container"> <div class="g-circle"></div> <ul class="g-bubbles"> <li class="g-bubble"></li> // ... 共 200 個 bubble 元素 <li class="g-bubble"></li> </ul> </div>
CSS:
.g-bubbles { position: absolute; width: 30px; height: 30px; transform: translate(-50%, -50%); left: 50%; top: 50%; border-radius: 50px; } .g-bubble { position: absolute; inset: 0; border-radius: 50%; background: hsl(60, 75%, 40%); }
得到這麼一個效果,所有圓形小點,都暫時匯聚在容器的中心:
這裡需要簡單解釋一下:
其次,我們藉助 SASS,按照元素的順序,把它們順序排列到圓環軌跡之上:
$count: 200; @for $i from 1 through $count { .g-bubble:nth-child(#{$i}) { --rotate: calc(#{360 / $count} * #{$i} * 1deg); transform: rotateZ(var(--rotate)) translate(135px, 0); opacity: 1; } }
由於我們設置了 div 小球的個數為 200 個,這樣,我們就得到了一圈由 200 個圓形小球形成的圓環:
接下來這一步非常重要,我們設定一個動畫:
- 讓每個小球在動畫的
75% ~ 100%
階段做透明度從 1 到 0 的變換,而0% ~ 75%
的階段保持透明度為 0 - 讓 200 個 div 依次進行這個動畫效果(利用負的 animation-delay,從 -0 到 -4000ms),整體上就能形成逐漸消失的效果
@for $i from 1 through $count { .g-bubble:nth-child(#{$i}) { --rotate: calc(#{360 / $count} * #{$i} * 1deg); --delayTime: calc(4000 * #{$i / $count} * -1ms); transform: rotateZ(var(--rotate)) translate(135px, 0); opacity: 1; animation: showAndHide 4000ms linear var(--delayTime) infinite; } } @keyframes showAndHide { 0% { opacity: 0; } 75% { opacity: 0; } 75.1% { opacity: 1; } 100% { opacity: 0; } }
這樣,我們就得到了一個圓形小球氣泡圍繞圓環漸次消失的效果:
配合上整個圓環,效果就會是這樣:
很接近了,但是沒有隨機的感覺,氣泡也沒有散開的動畫。解決的方案:
- 所以我們需要讓氣泡在執行透明度變化的同時,進行一個隨機的發散位移
- 小圓形氣泡的大小也可以帶上一點隨機,同時,在動畫過程逐漸縮小
當然,整個動畫的基礎,還是在容器設置了 濾鏡 blur() 和 contrast() 的加持之下的,這樣,我們給氣泡再補上隨機動畫散開及縮放的動畫:
@for $i from 1 through $count { .g-bubble:nth-child(#{$i}) { --rotate: calc(#{360 / $count} * #{$i} * 1deg); --delayTime: calc(4000 * #{$i / $count} * -1ms); --scale: #{0.4 + random(10) / 10}; --x: #{-100 + random(200)}px; --y: #{-100 + random(200)}px; transform: rotateZ(var(--rotate)) translate(135px, 0); opacity: 1; animation: showAndHide 4000ms linear var(--delayTime) infinite; } } @keyframes showAndHide { 0% { transform: rotateZ(var(--rotate)) translate(135px, 0); opacity: 0; } 75% { opacity: 0; } 75.1% { transform: rotateZ(var(--rotate)) translate(135px, 0) scale(var(--scale)); opacity: 1; } 100% { transform: rotateZ(var(--rotate)) translate(calc(135px + var(--x)), var(--y)) scale(.2); opacity: 0; } }
只看一圈的氣泡圓形,我們能得到了這樣的效果:
配合上圓環的效果:
配合上父容器的 filter: hue-rotate()
動畫,就能實現顏色的動態變換,得到我們最終想要的效果:
這樣,沒有了第一版本的違和感,整個效果也顯得比較自然。
整體代碼:
HTML:
<div class="g-container"> <div class="g-circle"></div> <ul class="g-bubbles"> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> <li class="g-bubble"></li> </ul> </div>
CSS:
body, html { width: 100%; height: 100%; display: grid; place-content: center; background: #000; overflow: hidden; } $count: 200; :root { --headColor: hsl(130, 75%, 75%); --endColor: hsl(60, 75%, 40%); } .g-container { position: relative; width: 300px; height: 300px; padding: 100px; filter: blur(3px) contrast(3); background: #000; animation: hueRotate 8s infinite linear; } .g-circle { position: relative; width: 300px; height: 300px; border-radius: 50%; background: conic-gradient( var(--headColor) 0, var(--headColor) 2%, hsl(120, 75%, 70%), hsl(110, 75%, 65%), hsl(100, 75%, 60%), hsl(90, 75%, 55%), hsl(80, 75%, 50%), hsl(70, 75%, 45%), var(--endColor) 16%, var(--endColor) 18%, transparent 18% ); mask: radial-gradient(transparent, transparent 119px, #000 120px, #000); -webkit-mask: radial-gradient(transparent, transparent 119px, #000 120px, #000); animation: rotate 4s infinite -700ms linear; &::before, &::after { content: ""; position: absolute; inset: 0; width: 32px; height: 32px; background: var(--headColor); top: 0; left: 135px; border-radius: 50%; } &::after { background: var(--endColor); left: unset; top: 80px; right: 10px; } } .g-bubbles { position: absolute; width: 30px; height: 30px; transform: translate(-50%, -50%); left: 50%; top: 50%; border-radius: 50px; } .g-bubble { position: absolute; border-radius: 50%; background: var(--endColor); } @for $i from 1 through $count { .g-bubble:nth-child(#{$i}) { --rotate: calc(#{360 / $count} * #{$i} * 1deg); --delayTime: calc(4000 * #{$i / $count} * -1ms); --width: 30px; --scale: #{0.4 + random(10) / 10}; --x: #{-100 + random(200)}px; --y: #{-100 + random(200)}px; width: var(--width); height: var(--width); transform: rotateZ(var(--rotate)) translate(135px, 0); opacity: 1; animation: showAndHide 4000ms linear var(--delayTime) infinite; } } @keyframes showAndHide { 0% { transform: rotateZ(var(--rotate)) translate(135px, 0); opacity: 0; } 75% { opacity: 0; } 75.1% { transform: rotateZ(var(--rotate)) translate(135px, 0) scale(var(--scale)); opacity: 1; } 100% { transform: rotateZ(var(--rotate)) translate(calc(135px + var(--x)), var(--y)) scale(.2); opacity: 0; } } @keyframes rotate { 100% { transform: rotate(-360deg); } } @keyframes hueRotate { 100% { filter: blur(3px) contrast(3) hue-rotate(360deg); } }