上面的圖是題目,而我們要做出幾乎一樣的樣子,題目中還有附上出題官方的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。
那今天就先到這裡,明天我們再繼續來玩下一題。