iT邦幫忙

2024 iThome 鐵人賽

DAY 26
0

題目

CSS Challenge Day14

  • 腳踏車卡片
    https://ithelp.ithome.com.tw/upload/images/20241006/2016940326edkkGqNJ.png

題目除了第一面的腳踏車卡片,還有滑鼠指上後的直升機卡片

  • 直升機卡片

https://ithelp.ithome.com.tw/upload/images/20241006/20169403WBPLRaRnlx.png

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

我做好的此題CSS Challeage解答

那麼我們就開始吧。

題目分析

這個題目要求我們製作一個滑鼠指上後會翻轉的卡片,大概列出項目如下:

  1. 正面卡片:腳踏車圖案
    • 地板移動的動畫
  2. 背面卡片:直升機圖案
    • 雲朵移動的動畫
  3. 滑鼠指上時卡片翻轉的動畫
    • 卡片需有3D視角

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
    .flip
      .bicycle
      .helicopter

因為架構很簡單,所以就先做基礎的開版。

  • .center:內部容器,負責定位黃色的卡片在畫面中間。
  • .flip:為了到時候要做旋轉的3D視角,所以還是要包一層子項目。
  • .pic.bicycle .pic.helicopter:翻轉的兩張圖,預計裡面會放 svg 跟雲朵/地板移動的動畫。

基礎樣式

$gray: #4B4841;
$yellow: #FBCE4E;
$cardW: 320px;
$cardH: 180px;

.frame {
    ...
    background: $gray;
}

先用我們第一天教的小工具來吸底色:

  • $gray:灰色的底色,感覺等等 svg 著色也會用到,所以先存起來。
  • $yellow:黃色的卡片底色
  • $cardW $cardH:黃色卡片的寬高
    .frame 加上基礎的底色,接著我們來制作裡面黃色卡片的部分。

黃色卡片

.center {
	position: absolute;
	top: 50%;
	left: 50%;
	width: $cardW;
	height: $cardH;
	transform: translate(-50%,-50%);
	perspective: 800px;
}

先將卡片的寬高跟 position: absolute 設定好。
這邊我們先不把黃色的底色寫在卡片上,因為這個 div 是我們拿來定位用的,等等要翻轉的是裡面的 flip,由於翻轉的時候底色跟卡片的陰影等等視覺上的要素也要一起翻轉,所以視覺上的設定會寫在裡面,而不會寫在外面用來定位的卡片上。


翻轉設定

.center {
    ...
    &:hover .flip {
		transform: rotateX(180deg);
	}
	.flip {
		width: 100%;
		height: 100%;
		box-shadow:8px 10px 15px 0px rgba(0,0,0,0.5);
		transition: all 1s ease-in-out;
		transform-style: preserve-3d;
		background-color: $yellow;
		border-radius: 3px;
	}
}

這邊我們就先設定卡片的視覺要素在 flip 上:
先把寬高設定到符合外面 .center 的高度,並且做好卡片的陰影。

  • transition,讓翻轉的過渡效果持續 1 秒。
  • transform-style: preserve-3d:保持子元素在 3D 空間中的翻轉效果,如果沒有加上,裡面的腳踏車跟直升機都不會吃到翻轉的效果。
    https://ithelp.ithome.com.tw/upload/images/20241006/20169403kfm1XlKQq1.png

接著就是翻轉的效果

  • &:hover .flip:這是當 card 滑鼠指上時,flip 的效果。
    https://ithelp.ithome.com.tw/upload/images/20241006/20169403pEcFUwgNEa.png
    圖中可以看到,我們的卡片雖然翻轉了,但陰影也跟著翻轉了,這不是我們要的,所以這時候就需要去修改 box-shadow
&:hover .flip {
	transform: rotateX(180deg);
	box-shadow: 8px -10px 15px 0px rgba(0,0,0,0.5);
}

這邊我們把 box-shadow 的 Y 軸部分往上做了 -10px 的位移,翻轉後剛剛好看起來就是往下 10px,符合我們一開始想要的樣子,影子也不會突兀,這樣翻轉的效果就做好了。接著我們就來制作裡面的兩張圖。


腳踏車

.bicycle
    img src="https://100dayscss.com/codepen/bycicle.svg"
    .street
        - for i in (1..9)
          div class="stripe-#{i}"

先在腳踏車區塊內放入官方提供的 svg 檔。
接著用一個 .street 把路面移動樣子的動畫都包起來。
然後使用迴圈來寫 stripe-1stripe-9 總共 9 個 div

然後我們先來設定 .bicycle, .helicopter 中間圖片的基本設定,是腳踏車跟直升機都會使用到的部分,我們就寫在這,避免程式碼重複。

腳踏車 & 直升機的共同設定

.center {
    ...
    .bicycle, .helicopter {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		backface-visibility: hidden;
		text-align: center;
		
		img {
			position: relative;
			animation-fill-mode: both;
		}
	}
}

先把共同的樣式設定上去。

  • position: absolute:將元素絕對定位,根據父容器的邊界定位。
  • top: 0 left: 0 right: 0 bottom: 0:將元素填滿父容器的上下左右,寬高隨父容器調整。
  • backface-visibility: hidden:隱藏背面,適用於 3D 翻轉動畫。
  • text-align: center:文字內容水平置中,這邊的文字內容就是 svg 本身。

然後是 svg 的共同設定:

  • position: relative:設定圖片為相對定位,根據正常文檔流來定位,但可使用 top、left 等進行偏移,這樣設定是為了等等我們要讓 svg 圖片作上下的位移定位用的。
  • animation-fill-mode: both:確保動畫執行後,圖片保持動畫開始和結束時的樣式。這使圖片在動畫結束時不會回到原狀,而是停留在動畫的最後一幀。

這時候剛長這樣,試著用滑鼠指上看看,如果翻轉後的畫面是黃色的空卡片,那就沒問題囉

腳踏車的顛簸動畫

看題目可以知道,腳踏車有一點點上下顛簸的感覺,就像真的騎在路面上一樣,我們就先來做這個動畫

@keyframes animate-bike {
	0%, 100% {
		transform: scaleY(1);
	}
	50% {
		transform: scaleY(1.03);
	}
}
  • @keyframes animate-bike:定義了一個名為 animate-bike 的動畫,用來控制元素的垂直縮放效果。
  • 0%, 100%:在動畫的起始和結束時,元素的 scaleY 變換設為 1,表示元素的高度保持不變。
  • 50%:動畫中途 (50%),元素的垂直縮放比例為 1.03,表示元素的高度增加了 3%,達到動畫的頂點,這麼做的用意是讓腳踏車在中間有些微(3%)的拉長的效果。

動畫作好之後,我們來設定在腳踏車上。

.bicycle {
	z-index: 2;
	transform: rotateX(0);
	
	img {
		top: 52px;
		animation: animate-bike .6s ease-in-out infinite;
    }
}
  • .bicycle
    • z-index: 2:將 .bicycle 放置在第二層,確保它位於其他元素之上。
    • transform: rotateX(0):沿 X 軸進行 0 度旋轉,實際上沒有視覺變化,這是為了之後我們翻轉直升機的時候用的。
  • img (自行車 svg 圖像):
    • top: 52px:將圖片向下移動 52px。
    • animation: animate-bike .6s ease-in-out infinite
      • 對圖片應用名為 animate-bike 的動畫。
      • .6s:動畫持續時間為 0.6 秒。
      • ease-in-out:動畫速度曲線,表示動畫以較慢的速度開始和結束,中間速度較快。
      • infinite:動畫無限次重複,形成循環效果。

https://ithelp.ithome.com.tw/upload/images/20241006/201694031BCbRtjsuE.png

地面移動的動畫

.bicycle {
    ...
    .street {
		position: absolute;
		top: 127px;
		left: 80px;
		width: 160px;
		height: 3px;
		overflow: hidden;
	}
}

先來設定地面移動的線條他所在的位置。

  • position: absolute:設定絕對定位,讓它可以依據父項 bicycle 制作定位。
  • top: 127px left: 80px:設定頂部與左側的距離,確保街道正確位於自行車下方。
  • width: 160px height: 3px:設定街道的寬度為 160px,並且高度為 3px,模擬窄的地面線條。
  • overflow: hidden:隱藏超出這個區域的內容,之後我們會設定位移的動畫,位移到超出這個範圍的就會被隱藏。

接著就先來設定位移的動畫:

@keyframes animate-speed {
	0% {
		transform: translate3d(0, 0, 0) scaleX(1);
	}
	100% {
		transform: translate3d(-210px,0,0) scaleX(.8);
	}
}

由於底下我們在 HTML 中使用 @for 迴圈來設定了很多條線,我們現在要讓這幾條線在位移到最左邊的時候都慢慢變窄,所以這邊是結合了位移跟視覺上變窄的動畫。

  • 0% 狀態:
    • transform: translate3d(0, 0, 0) scaleX(1)
    • 沒有移動,元素保持在初始位置,水平比例 scaleX(1),即寬度不變。
  • 100% 狀態:
    • transform: translate3d(-210px, 0, 0) scaleX(.8)
    • 水平向左平移 210px,縮小水平比例到 scaleX(.8),表示寬度縮小至原始的 80%。

再來就是設定 @for 迴圈中街道的線條:

.street {
    ...
    @for $i from 1 through 9 {
        .stripe-#{$i} {
            position: absolute;
			right: -25px;
			top: 0;
			height: 3px;
			width: (2 + $i * 2) + px;
			background: $gray;
			border-radius: 3px;
			animation: animate-speed (0.8 + random(2) / 10) + s 
                       linear (random(10) / 10) + s infinite;
		}	
	}
}

這段程式碼的詳細解說如下:
@for $i from 1 through 9:使用 @for 循環語法,會根據 $i 生成 9 個 .stripe 元素,並給每個元素動態分配不同的屬性。

  • 位置與大小:

    • position: absolute:每個 .stripe 元素的定位方式是絕對定位,根據父元素 .street 的相對位置來決定。
    • right: -25px:設定每條線條距離父元素的右邊界 25px。
    • top: 0:所有的線條在父元素的頂部對齊。
    • height: 3px:剛剛我們父元素的高度是 3px,這邊我也設定每條線條的高度固定為 3px,讓它們看起來是一致的。
    • width: (2 + $i * 2) + px:線條寬度根據 $i 變數來動態增長。隨著 $i 的增加,寬度變成 2px + 2 * i px,即從第一條線的 4px 開始,逐漸變寬。
  • 背景樣式:

    • background: $gray:讓每條線有灰色的底色。
  • 圓角效果:

    • border-radius: 3px:設定線條兩端的圓角效果。
  • 動畫效果:

    • animate-speed:每個 .stripe 會根據這個動畫來移動。
    • (0.8 + random(2) / 10) + s:定義動畫持續時間。random(2) 生成一個 0 到 2 之間的隨機數,再除以10,讓每條線的動畫時間不同(約為 0.8 秒到 1 秒左右),讓線條動畫看起來更隨機自然。
    • linear:指定動畫速率是線性的,整個動畫過程中的速度保持一致。
    • (random(10) / 10) + s:設定每條線動畫開始的延遲時間,讓不同線條不會同時開始動畫。每條線的延遲時間會在 0 到 1 秒之間隨機生成。
    • infinite:動畫會無限次重複執行。

線條的部分剛做好的時候就長這樣
https://ithelp.ithome.com.tw/upload/images/20241006/201694038XqNyfaX4D.png

由於篇幅太長了,我決定分篇寫,下一篇再來寫後面直升機的部分。


Wrap up and go home

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

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


上一篇
Day 25 - CSS Challenge #13:User Gallery(下)
下一篇
Day 27 - CSS Challenge #14:Flip Card(下)
系列文
Dive into CSS Challenge:從問題到解決方案的實踐之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言