使用 Flex 和 Grid 佈局實現 3D 骰子!
大家好,我是 CUGGZ。
在前端面試中,經常會問到如何使用 CSS 實現骰子 / 麻將佈局。今天我們就來用 CSS 創建一個 3D 骰子,通過本文可以學到;
-
使用
transform
來實現 3D 形狀; -
給 3D 骰子實現旋轉動畫;
-
使用 Flex 佈局來實現骰子佈局;
-
使用 Grid 佈局來實現骰子佈局。
- 使用 Flex 佈局實現六個面
首先,來定義骰子六個面的 HTML 結構:
<div class="dice-box">
<div class="dice first-face">
<span class="dot"></span>
</div>
<div class="dice second-face">
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="dice third-face">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="dice fourth-face">
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
<div class="fifth-face dice">
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
<div class="dice sixth-face">
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
</div>
下面來實現每個面和每個點的的基本樣式:
.dice {
width: 200px;
height: 200px;
padding: 20px;
background-color: tomato;
border-radius: 10%;
}
.dot {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: white;
}
實現效果如下:
(1)一個點
HTML 結構如下:
<div class="dice first-face">
<span class="dot"></span>
</div>
實現第一個面,只需要讓它水平和垂直方向都居中即可:
-
justify-content:center
:使點與主軸(水平)的中心對齊。 -
align-items:center
:使點與交叉軸(垂直)的中心對齊。
代碼實現如下:
.first-face {
display: flex;
justify-content: center;
align-items: center;
}
現在第一面是這樣的:
(2)兩個點
HTML 結構如下:
<div class="dice second-face">
<span class="dot"></span>
<span class="dot"></span>
</div>
首先來將第二個面的父元素設置爲 flex 佈局,並添加以下屬性:
justify-content: space-between
:將子元素放置在 flex 容器的開頭和結尾。
.second-face {
display: flex;
justify-content : space-between;
}
現在點的位置如下:
這時,第一個點在正確的位置:左上角。而第二個點需要在右下角。因此,下面來使用 align-self 屬性單獨調整第二個點的位置:
align-self: flex-end
:將項目對齊到 Flex 容器的末尾。
.second-face .dot:nth-of-type(2) {
align-self: flex-end;
}
現在第二面是這樣的:
(3)三個點
HTML 結構如下:
<div class="dice third-face">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
可以通過在第二面放置另一箇中心點來實現第三面。
-
align-self: flex-end
:將項目對齊到 Flex 容器的末尾。 -
align-self: center
:將項目對齊到 Flex 容器的中間。
.third-face {
display: flex;
justify-content : space-between;
}
.third-face .dot:nth-of-type(2) {
align-self: center;
}
.third-face .dot:nth-of-type(3) {
align-self: flex-end;
}
現在第三面是這樣的:
如果想要第一個點在右上角,第三個點在左下角,可以將第一個點的 align-self 更改爲 flex-end,第二個點不變,第三個點無需設置,默認在最左側:
.third-face {
display: flex;
justify-content : space-between;
}
.third-face .dot:nth-of-type(1) {
align-self :flex-end;
}
.third-face .dot:nth-of-type(2) {
align-self :center;
}
現在第三面是這樣的:
(4)四個點
HTML 結構如下:
<div class="dice fourth-face">
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
在四個點的面中,可以將其分爲兩行,每行包含兩列。一行將在 flex-start ,另一行將在 flex-end 。並添加 justify-content: space-between 以便將其放置在骰子的左側和右側。
.fourth-face {
display: flex;
justify-content: space-between
}
接下來需要對兩列點分別進行佈局:
-
將列設置爲
flex
佈局; -
將
flex-direction
設置爲column
,以便將點放置在垂直方向上 -
將
justify-content
設置爲space-between
,它將使第一個點在頂部,第二個點在底部。
.fourth-face .column {
display: flex;
flex-direction: column;
justify-content: space-between;
}
現在第四面是這樣的:
(5)五個點
HTML 結構如下:
<div class="fifth-face dice">
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
第五面和第四面的差異在於多了中間的那個點。所以,可以基於第四面,來在中間增加一列,樣式如下:
.fifth-face {
display: flex;
justify-content: space-between
}
.fifth-face .column {
display: flex;
flex-direction: column;
justify-content: space-between;
}
現在第五面是這樣的:
還需要對中間的點進行調整,可以設置 justify-content 爲 center 讓它垂直居中:
.fifth-face .column:nth-of-type(2) {
justify-content: center;
}
現在第五面是這樣的:
(6)六個點
HTML 結構如下:
<div class="dice sixth-face">
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="column">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
第六個面的佈局和第四個幾乎完全一樣,只不過每一列多了一個元素,佈局實現如下:
.sixth-face {
display: flex;
justify-content: space-between
}
.sixth-face .column {
display: flex;
flex-direction: column;
justify-content: space-between;
}
現在第六面是這樣的:
- 使用 Grid 佈局實現六個面
骰子每個面其實可以想象成一個 3 x 3 的網格,其中每個單元格代表一個點的位置:
+---+---+---+
| a | b | c |
+---+---+---+
| d | e | f |
+---+---+---+
| g | h | i |
+---+---+---+
要創建一個 3 x 3 的網格,只需要設置一個容器元素,並且設置三個大小相同的行和列:
.dice {
display: grid;
grid-template-rows: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
}
這裏的 fr 單位允許將行或列的大小設置爲網格容器可用空間的一部分,這上面的例子中,我們需要三分之一的可用空間,所以設置了 1fr 三次。
我們還可以使用 repeat(3, 1fr) 將 1fr 重複 3 次,來代替 1fr 1fr 1fr。還可以使用定義行 / 列的 grid-template
速記屬性將上述代碼進行簡化:
.dice {
display: grid;
grid-template: repeat(3, 1fr) / repeat(3, 1fr);
}
每個面所需要定義的 HTML 就像是這樣:
<div class="dice">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
所有的點將自動放置在每個單元格中,從左到右:
現在我們需要爲每個骰子值定位點數。開始時我們提到,可以將每個面分成 3 x 3 的表格,但是這些表格並不是每一個都是我們需要的,分析骰子的六個面,可以發現,我們只需要以下七個位置的點:
+---+---+---+
| a | | c |
+---+---+---+
| e | g | f |
+---+---+---+
| d | | b |
+---+---+---+
我們可以使用 grid-template-areas
屬性將此佈局轉換爲 CSS:
.dice {
display: grid;
grid-template-areas:
"a . c"
"e g f"
"d . b";
}
因此,我們可以不使用傳統的單位來調整行和列的大小,而只需使用名稱來引用每個單元格。其語法本身提供了網格結構的可視化,名稱由網格項的網格區域屬性定義。中間列中的點表示一個空單元格。
下面來使用 grid-area
屬性爲網格項命名,然後,網格模板可以通過其名稱引用該項目,以將其放置在網格中的特定區域中。:nth-child()
僞選擇器允許單獨定位每個點。
.dot:nth-child(2) {
grid-area: b;
}
.dot:nth-child(3) {
grid-area: c;
}
.dot:nth-child(4) {
grid-area: d;
}
.dot:nth-child(5) {
grid-area: e;
}
.dot:nth-child(6) {
grid-area: f;
}
現在六個面的樣式如下:
可以看到,1、3、5 的佈局仍然是不正確的,只需要重新定位每個骰子的最後一個點即可:
.dot:nth-child(odd):last-child {
grid-area: g;
}
這時所有點的位置都正確了:
對於上面的 CSS,對應的 HTML 分別是父級爲一個 div 標籤,該面有幾個點,子級就有幾個 span 標籤。代碼如下:
<div class="dice-box">
<div class="dice first-face">
<span class="dot"></span>
</div>
<div class="dice second-face">
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="dice third-face">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="dice fourth-face">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="fifth-face dice">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
<div class="dice sixth-face">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</div>
</div>
整體的 CSS 代碼如下:
.dice {
width: 200px;
height: 200px;
padding: 20px;
background-color: tomato;
border-radius: 10%;
display: grid;
grid-template: repeat(3, 1fr) / repeat(3, 1fr);
grid-template-areas:
"a . c"
"e g f"
"d . b";
}
.dot {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: white;
}
.dot:nth-child(2) {
grid-area: b;
}
.dot:nth-child(3) {
grid-area: c;
}
.dot:nth-child(4) {
grid-area: d;
}
.dot:nth-child(5) {
grid-area: e;
}
.dot:nth-child(6) {
grid-area: f;
}
.dot:nth-child(odd):last-child {
grid-area: g;
}
- 實現 3D 骰子
上面我們分別使用 Flex 和 Grid 佈局實現了骰子的六個面,下面來這將六個面組合成一個正方體。
首先對六個面進行一些樣式修改:
.dice {
width: 200px;
height: 200px;
padding: 20px;
box-sizing: border-box;
opacity: 0.7;
background-color: tomato;
position: absolute;
}
定義它們的父元素:
.dice-box {
width: 200px;
height: 200px;
position: relative;
transform-style: preserve-3d;
transform: rotateY(185deg) rotateX(150deg) rotateZ(315deg);
}
其中,transform-style: preserve-3d;
表示所有子元素在 3D 空間中呈現。這裏的 transform 的角度不重要,主要是便於後面查看。
此時六個面的這樣的:
看起來有點奇怪,所有面都疊加在一起。不要急,我們來一個個調整位置。
首先將第一個面在 Z 軸移動 100px:
.first-face {
transform: translateZ(100px);
}
第一面就到了所有面的上方:
因爲每個面的寬高都是 200px,所以將第六面沿 Z 軸向下調整 100px:
.sixth-face {
transform: translateZ(-100px);
}
第六面就到了所有面的下方:
下面來調整第二面,將其在 X 軸向後移動 100px,並沿着 Y 軸旋轉 -90 度:
.second-face {
transform: translateX(-100px) rotateY(-90deg);
}
此時六個面是這樣的:
.fifth-face {
transform: translateX(100px) rotateY(90deg);
}
此時六個面是這樣的:
下面來調整第三面,道理同上:
.third-face {
transform: translateY(100px) rotateX(90deg);
}
此時六個面是這樣的:
最後來調整第五面:
.fourth-face {
transform: translateY(-100px) rotateX(90deg);
}
此時六個面就組成了一個完整的正方體:
下面來爲這個骰子設置一個動畫,讓它轉起來:
@keyframes rotate {
from {
transform: rotateY(0) rotateX(45deg) rotateZ(45deg);
}
to {
transform: rotateY(360deg) rotateX(45deg) rotateZ(45deg);
}
}
.dice-box {
animation: rotate 5s linear infinite;
}
最終的效果如下:
在線體驗:
-
3D 骰子 - Flex:https://codepen.io/cugergz/pen/jOzYGyV
-
3D 骰子 - Grid:https://codepen.io/cugergz/pen/GROMgEe
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/JKvZJS2yyNjFF4OPFdkQ8g