題目除了第一面的腳踏車卡片,還有滑鼠指上後的直升機卡片
上面的圖是題目,而我們要做出幾乎一樣的樣子,題目中還有附上出題官方的CodePen,也有附上給我們解題用的template,當我們真的不會的時候,還是可以參考他們的寫法,所以沒有想像中困難。
我做好的此題CSS Challeage解答
那麼我們就開始吧。
這個題目要求我們製作一個滑鼠指上後會翻轉的卡片,大概列出項目如下:
因為我並不想要放好幾個一樣的 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 空間中的翻轉效果,如果沒有加上,裡面的腳踏車跟直升機都不會吃到翻轉的效果。接著就是翻轉的效果
&:hover .flip
:這是當 card
滑鼠指上時,flip
的效果。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-1
到 stripe-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
:動畫無限次重複,形成循環效果。.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
:動畫會無限次重複執行。線條的部分剛做好的時候就長這樣
由於篇幅太長了,我決定分篇寫,下一篇再來寫後面直升機的部分。
希望改變了這種按照步驟的寫法,能讓更多人看得懂,也能跟我一樣喜歡上寫CSS。
那今天就先到這裡,明天我們再繼續來玩下一集。