上面的圖是題目,而我們要做出幾乎一樣的樣子,題目中還有附上出題官方的CodePen,也有附上給我們解題用的template,當我們真的不會的時候,還是可以參考他們的寫法,所以沒有想像中困難。
我做好的此題CSS Challeage解答
那麼我們就開始吧。
這個題目是一個純粹的動畫,其實題目中的元素總共只有三種:
而整體上來說,它們在做的動畫其實是同一個,都是 rotate
,都是照著順時鐘在轉。
因為我並不想要放好幾個一樣的 div
在裡面,所以在這裡我跟題目一樣使用了 slim 版本的 HTML 預處理器來寫。
Slim 是一個輕量化的模板語言,它使用縮排來結構化 HTML,簡化了標籤的書寫,不需要閉合標籤,並減少了重複的代碼。這樣可以讓 HTML 更加簡潔易讀,常用於像 Ruby on Rails 這樣的框架中。
例如,傳統 HTML 的寫法:
<div class="container">
<h1>Hello World</h1>
</div>
在 Slim 中可以這樣寫:
.container
h1 Hello World
這種語法可以大幅減少 HTML 的冗餘,使模板更易維護。
.frame
.center
.ball
- for i in (1..8)
div class="bubble-#{i}"
- for i in (1..10)
div class="hanabi-#{i}"
.frame
和 .center
容器內動態生成幾個 div
元素:.frame
和 .center
是兩個嵌套的容器,用來定位和控制內部元素。.ball
是一個靜態元素,用來與動態生成的元素互動。for
迴圈動態生成 1 到 8 的 div
,每個 div
的 class 名稱是 bubble-1
到 bubble-8
。for
迴圈動態生成 1 到 10 的 div
,每個 div
的 class 名稱是 hanabi-1
到 hanabi-10
。這樣的結構可以方便我們生成多個動畫元素,比如「泡泡」或「花火」,在畫面中進行動畫顯示。
把 .frame
的底色改成黑色,並加上 filter: contrast(15)
之後,就可以開始了。
filter: contrast(15)
是用來調整元素的對比度的屬性。contrast 可以調整顯示中物件的明暗對比,數值 1 代表原始對比度。當設置為 15 時,表示對比度被大幅提高,讓元素內部的顏色差異更加明顯,淺色變得更亮,深色變得更暗。這會使畫面更具有強烈的視覺效果,特別是用在模糊元素上時,能夠讓模糊邊緣更加顯著。
在這邊加上這句是為了讓後面動畫只有在邊緣的地方會模糊,如果沒加上的話,畫面會是這樣:
@keyframes animateRotate {
from {
transform: rotate(0deg) translate3d(0, 0, 0);
}
to {
transform: rotate(360deg) translate3d(0, 0, 0);
}
}
因為等等所有環節都會用到這個動畫,所以我們先把它寫起來。
我們先做一個 keyframes
並取名為 animateRotate
,他的功用是讓元素從初始的位置開始順時針旋轉一整圈(0度到360度)。具體來說:
from
定義動畫的起點,元素從 rotate(0deg)
開始,這表示不旋轉。to
定義動畫的終點,元素旋轉至 rotate(360deg)
,即一個完整的圓周。translate3d(0, 0, 0)
則保持元素在 3D 空間內的位置不變。這段動畫會實現元素的 360 度旋轉效果。
$ballWH: 90px;
.ball {
position: relative;
width: $ballWH;
height: $ballWH;
background-color: #fff;
border-radius: 50%;
filter: blur(15px);
}
接著我們來畫母體圓點 .ball
。
先使用 position: relative
進行定位,讓它成為一個畫布,允許內部元素相對於它進行定位。
一樣使用變數來設定寬高,並將顏色改成白色,並做出圓角效果。
$bubbleWH: 50px;
$bubbleLeftTop: ($ballWH - $bubbleWH) / 2;
@for $i from 1 through 8 {
.bubble-#{$i} {
position: absolute;
left: $bubbleLeftTop;
top: $bubbleLeftTop;
width: $bubbleWH;
height: $bubbleWH;
transform: rotate((random(300)) + deg);
&:after {
content: '';
display: block;
position: absolute;
width: $bubbleWH;
height: $bubbleWH;
background-color: #fff;
border-radius: 50%;
animation: animateRotate (2.5 + $i / 5) + s //旋轉速度
ease-in-out ($i / 5)+ s //淡出入速度
infinite; //永久持續
filter: blur(5px);
transform-origin: (40 - $i * 3) + px (40 - $i * 3) + px; //每一個都偏移
}
}
}
這區我使用 Sass
來寫,經由 @for
迴圈生成了 8 個 bubble
的 div
,取名叫做 bubble-1
~ bubble-8
。
它們是絕對定位在 .ball
內的,每個 bubble
是以隨機且逐漸遞增的方式動態生成不同的位置、旋轉角度跟動畫參數。使用 transform-origin
控制旋轉中心點的偏移,創造了在不同位置都會有波浪的產生。
再來,使用 &:after
產生偽類,模仿 bubble 的動畫,設定了淡入淡出的效果和旋轉動畫。再使用 animation
和 transform-origion
的參數基於迴圈的索引 $i
動態改變,使得每個氣泡都有不同的旋轉速度和位置,這也實現了波浪大小的不同。
由此可知道,我們在圓點上,產生的波浪位置是隨機的,他可能在 360 度的各個角度內出現波浪,而波浪的大小也是隨機的。
並且在它們身上都加上了 filter: blur(5px)
讓它們視覺上是一個邊緣有點模糊的白球,進而讓動畫整體更自然不生硬。
接著讓我們來細說這些比較需要注意的部分:
transform
:rotate((random(300)) + deg)
:這代表著每個 bubble
的旋轉角度是隨機設定的,範圍在 0 到 300 度之間。random(300)
則產生一個隨機值。這讓每個波浪在不同角度出現。
animation
和 $i
:animation: animateRotate (2.5 + $i / 5) + s
:這段控制旋轉動畫的總時間。根據 $i
的值,每個氣泡的旋轉速度會有所不同。 $i / 5
增加了每個氣泡旋轉時間的差異,2.5
是基本旋轉時間單位,隨著 $i
的增大,旋轉的速度會稍微變慢。ease-in-out ($i / 5) + s
:控制淡入淡出的效果,透過將 $i
代入計算每個氣泡的動畫延遲時間,延遲值是 $i / 5
,因此每個氣泡開始動畫的時間都會有所不同,形成視覺上的遞進效果。
transform-origin
和 $i
:transform-origin: (40 - $i * 3) + px (40 - $i * 3) + px
:這部分控制每個氣泡的旋轉中心點。隨著 $i
的增大,旋轉中心會從 (40px, 40px)
向內縮小,每個氣泡的旋轉中心點會有所不同,使每個氣泡的旋轉效果不一致,增加了動畫的多樣性。
$hanabiLeftTop: 38px;
@for $i from 1 through 10 {
.hanabi-#{$i} {
position: absolute;
left: $hanabiLeftTop;
top: $hanabiLeftTop;
width: (7 + $i) + px;
height: (7 + $i) + px;
transform: rotate((random(300)) + deg);
&:after {
content: '';
display: block;
position: absolute;
width: (7 + $i) + px;
height: (7 + $i) + px;
background-color: #fff;
border-radius: 50%;
transform-origin: (60 - $i * 2) + px (60 - $i * 2) + px; //每一個都偏移
animation: animateRotate (3.5 + $i / 5) + s //旋轉速度
ease-in-out ($i / 5)+ s //淡出入速度
infinite; //永久持續
filter: blur(3px);
}
}
}
其實這邊跟上面 bubble
的部分幾乎是一樣的概念。
這區我使用 Sass
來寫,經由 @for
迴圈生成了 10 個 hanabi
的 div
,取名叫做 hanabi-1
~ hanabi-10
。
width: (7 + $i) + px; 和 height: (7 + $i) + px;
$i
從 1 到 10 循環,因此每個 .hanabi
元素的寬高是 7px 加上 $i
,從 8px 到 17px 隨著每個元素遞增。這讓每個 hanabi
圓形的大小逐漸變大,模擬煙火效果。它們使用了幾乎相同的 css 及 html 架構,在修改了寬高及旋轉的時間、旋轉的中心點,等等,就創造了兩個視覺上看起來並不相同的動畫,進而創造了視覺上的層次感。
而這邊比較關鍵的是使用了 filter: blur()
應用模糊效果。
當我們使用 filter: blur()
時,這個屬性會在元素的邊緣和背景之間製造模糊效果,這讓元素看起來與背景或其他元素「融合」在一起,減少了邊緣的銳利度。這樣的效果能夠使快速移動的物件(像動畫中的圓球)看起來更柔和和平滑,不會顯得過於突兀。
在 ball
、bubble
和 hanabi
元素中,模糊效果提供了一個自然的過渡,視覺上讓這些元素在動畫運行時具有動態、光暈式的融合感,這有助於形成更柔和的動畫效果。如果沒有 filter: blur()
,這些圓形會顯得銳利且生硬,缺乏動畫過程中的柔和過渡,使整個動畫顯得不自然。
這邊附上如果沒有加上 filter: blur()
的圖片給大家做比對。
希望改變了這種按照步驟的寫法,能讓更多人看得懂,也能跟我一樣喜歡上寫CSS。
那今天就先到這裡,明天我們再繼續來玩下一題。