iT邦幫忙

2024 iThome 鐵人賽

DAY 13
0
Modern Web

Dive into CSS Challenge:從問題到解決方案的實踐之旅系列 第 13

CSS Challenge Day #8:Metaballs use CSS filter for animation

  • 分享至 

  • xImage
  •  

題目

CSS Challenge Day8
https://ithelp.ithome.com.tw/upload/images/20240926/20169403zYPrvO1XI9.png

上面的圖是題目,而我們要做出幾乎一樣的樣子,題目中還有附上出題官方的CodePen,也有附上給我們解題用的template,當我們真的不會的時候,還是可以參考他們的寫法,所以沒有想像中困難。

我做好的此題CSS Challeage解答

那麼我們就開始吧。

題目分析

這個題目是一個純粹的動畫,其實題目中的元素總共只有三種:

  • 中心圓點:圓形的中間固定區塊。
  • 波浪:他是中心圓點身上的泡沫,他的存在讓中心的圓點看起來好像海浪一樣往隨機動態的四周擴張。
  • 水珠動畫:它們會順時鐘噴飛出去,飛出去後各自的軌跡又是順時鐘轉一圈,最後回到原點,看起來就像散開的火花一樣。

而整體上來說,它們在做的動畫其實是同一個,都是 rotate ,都是照著順時鐘在轉。

HTML Preprocessor

https://ithelp.ithome.com.tw/upload/images/20240926/20169403ODPOGkkVeM.png

因為我並不想要放好幾個一樣的 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}"
  • 我先使用預設的 template,在 .frame.center 容器內動態生成幾個 div 元素:
  • .frame.center 是兩個嵌套的容器,用來定位和控制內部元素。
  • .ball 是一個靜態元素,用來與動態生成的元素互動。
  • 第一個 for 迴圈動態生成 1 到 8 的 div,每個 div 的 class 名稱是 bubble-1bubble-8
  • 第二個 for 迴圈動態生成 1 到 10 的 div,每個 div 的 class 名稱是 hanabi-1hanabi-10

這樣的結構可以方便我們生成多個動畫元素,比如「泡泡」或「花火」,在畫面中進行動畫顯示。

樣式

.frame 的底色改成黑色,並加上 filter: contrast(15) 之後,就可以開始了。

filter: contrast(15) 是用來調整元素的對比度的屬性。contrast 可以調整顯示中物件的明暗對比,數值 1 代表原始對比度。當設置為 15 時,表示對比度被大幅提高,讓元素內部的顏色差異更加明顯,淺色變得更亮,深色變得更暗。這會使畫面更具有強烈的視覺效果,特別是用在模糊元素上時,能夠讓模糊邊緣更加顯著。

在這邊加上這句是為了讓後面動畫只有在邊緣的地方會模糊,如果沒加上的話,畫面會是這樣:
https://ithelp.ithome.com.tw/upload/images/20240927/20169403LdskQcYDzH.png

旋轉動畫

@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 進行定位,讓它成為一個畫布,允許內部元素相對於它進行定位。
一樣使用變數來設定寬高,並將顏色改成白色,並做出圓角效果。

https://ithelp.ithome.com.tw/upload/images/20240926/20169403bwmCEL5YnA.png

波浪

$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 個 bubblediv,取名叫做 bubble-1 ~ bubble-8

它們是絕對定位在 .ball 內的,每個 bubble 是以隨機且逐漸遞增的方式動態生成不同的位置、旋轉角度跟動畫參數。使用 transform-origin 控制旋轉中心點的偏移,創造了在不同位置都會有波浪的產生。

再來,使用 &:after 產生偽類,模仿 bubble 的動畫,設定了淡入淡出的效果和旋轉動畫。再使用 animationtransform-origion 的參數基於迴圈的索引 $i 動態改變,使得每個氣泡都有不同的旋轉速度和位置,這也實現了波浪大小的不同。

由此可知道,我們在圓點上,產生的波浪位置是隨機的,他可能在 360 度的各個角度內出現波浪,而波浪的大小也是隨機的。

並且在它們身上都加上了 filter: blur(5px) 讓它們視覺上是一個邊緣有點模糊的白球,進而讓動畫整體更自然不生硬。

接著讓我們來細說這些比較需要注意的部分:

  1. transform
    rotate((random(300)) + deg):這代表著每個 bubble 的旋轉角度是隨機設定的,範圍在 0 到 300 度之間。random(300) 則產生一個隨機值。這讓每個波浪在不同角度出現。

  2. animation$i
    animation: animateRotate (2.5 + $i / 5) + s:這段控制旋轉動畫的總時間。根據 $i 的值,每個氣泡的旋轉速度會有所不同。 $i / 5 增加了每個氣泡旋轉時間的差異,2.5 是基本旋轉時間單位,隨著 $i 的增大,旋轉的速度會稍微變慢。
    ease-in-out ($i / 5) + s:控制淡入淡出的效果,透過將 $i 代入計算每個氣泡的動畫延遲時間,延遲值是 $i / 5,因此每個氣泡開始動畫的時間都會有所不同,形成視覺上的遞進效果。

  3. transform-origin$i
    transform-origin: (40 - $i * 3) + px (40 - $i * 3) + px:這部分控制每個氣泡的旋轉中心點。隨著 $i 的增大,旋轉中心會從 (40px, 40px) 向內縮小,每個氣泡的旋轉中心點會有所不同,使每個氣泡的旋轉效果不一致,增加了動畫的多樣性。

https://ithelp.ithome.com.tw/upload/images/20240926/20169403VMqlATF2d1.png

水珠動畫

$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 個 hanabidiv,取名叫做 hanabi-1 ~ hanabi-10

  • 寬高的變化:width: (7 + $i) + px; 和 height: (7 + $i) + px;
    $i 從 1 到 10 循環,因此每個 .hanabi 元素的寬高是 7px 加上 $i,從 8px 到 17px 隨著每個元素遞增。這讓每個 hanabi 圓形的大小逐漸變大,模擬煙火效果。

https://ithelp.ithome.com.tw/upload/images/20240926/201694036o7LR0BKrD.png

總結

它們使用了幾乎相同的 css 及 html 架構,在修改了寬高及旋轉的時間、旋轉的中心點,等等,就創造了兩個視覺上看起來並不相同的動畫,進而創造了視覺上的層次感。

而這邊比較關鍵的是使用了 filter: blur() 應用模糊效果。
當我們使用 filter: blur() 時,這個屬性會在元素的邊緣和背景之間製造模糊效果,這讓元素看起來與背景或其他元素「融合」在一起,減少了邊緣的銳利度。這樣的效果能夠使快速移動的物件(像動畫中的圓球)看起來更柔和和平滑,不會顯得過於突兀。

ballbubblehanabi 元素中,模糊效果提供了一個自然的過渡,視覺上讓這些元素在動畫運行時具有動態、光暈式的融合感,這有助於形成更柔和的動畫效果。如果沒有 filter: blur(),這些圓形會顯得銳利且生硬,缺乏動畫過程中的柔和過渡,使整個動畫顯得不自然。

https://ithelp.ithome.com.tw/upload/images/20240926/201694039x6fdkCRuE.png
這邊附上如果沒有加上 filter: blur() 的圖片給大家做比對。


Wrap up and go home

希望改變了這種按照步驟的寫法,能讓更多人看得懂,也能跟我一樣喜歡上寫CSS。

那今天就先到這裡,明天我們再繼續來玩下一題。


上一篇
CSS Challenge Day #7:Notification、Menu、Search(下)
下一篇
CSS Challenge Day #9:Rainy Night - Weather UI (上)
系列文
Dive into CSS Challenge:從問題到解決方案的實踐之旅14
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
danny101201
iT邦新手 3 級 ‧ 2024-09-27 15:28:57

感謝校稿XD

我要留言

立即登入留言