春節是中國人最重要的節日,春節期間的習俗也非常多,東西南北各不相同。 為了增添年味,過年時家家戶戶會置辦各種年貨和裝飾品,把家裡營造得紅紅火火,紅燈籠,紅對聯,紅福字,以及紅色的中國結。
中國結原材料是簡簡單單的紅繩,經過古人的巧妙構思,編織成一個菱形網格的樣子。網格上的繩線緊緊連線在一起,象徵一家人團結和睦,幸福美滿。
那麼如何用CSS來實現一箇中國結呢? 先看最終效果。
線上預覽Codepen地址:
https://codepen.io/cliea/pen/LYOPbBr
如此 Amazing 的效果,你也可以做出來,下面讓我們開始吧!
先從網上搜一張中國結的圖片,中國結的樣式不止一種,我們選擇一種最經典的中國結的編織樣式。 圖片的品質決定了最後成品的品質,下面就是一張比較整潔,結構清晰的中國結圖片。供我們寫CSS時參考使用。
有了圖片就可以開始寫程式碼了嗎?當然不是。
首先回想一下現在要做的事:用CSS畫箇中國結。
你真的想好了嗎?這是一個可以實現的目標嗎?想象一下,當你的領導給你佈置一個任務:讓手機殼根據APP的主題色而變色。你會直接開始寫程式碼嗎?
你會想兩個問題:
APP作為一個軟體,是否有和手機殼互動的介面
手機殼如果接收到色值,如何變色
這是一個比較極端的例子,上面兩條都無法實現。回到CSS和這張中國結的圖片。我們首先要想的是,我們應該用哪些CSS技術,來實現這個圖片。你現在回過頭仔細觀察一下上面的圖片。
經過短暫的觀察,我們發現這樣一些要點:
中國結的繩子是由漸變色組成,深紅,淺紅,深紅
中間的主體部分由22根相互交叉的繩子組合而成,而且每過一個交叉點就會交換一下層級順序
有一些環狀結構,顏色漸變的過程與直線相同
整體都是紅色,以黃色點綴
然後就是預想一下實現原理:
直線的顏色漸變,使用 linear-gradient
或者 repeating-linear-gradient
環狀漸變,使用 radial-gradient
網格的交叉,使用 mask
遮罩來達到交叉效果
四分之三環以及底部兩根彎曲的繩子使用 clip-path
來裁剪
為了使編碼更方便,採用 SCSS
許多地方可以使用 ::before
::after
實現,減少html
程式碼
上面是從技術角度從整體觀察,下面就是對整個圖片進行拆分,先確定其 html
結構。
html
標籤180deg
header
footer
這樣我們得到了 html
的結構
<div class="chinese-knot"> <div class="grid"></div> <div class="ring-small"> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> </div> <div class="ring-big"> <i><b></b></i> <i><b></b></i> </div> <div class="cross-node"> <div class="node"> <i></i> <i></i> <i></i> <i></i> </div> <div class="node"> <i></i> <i></i> <i></i> <i></i> </div> </div> <div class="header"> <i></i> <b></b> <span></span> </div> <div class="footer"> <b></b> <b></b> <div class="tassels"> <i></i> <i></i> </div> </div> </div>
實際編碼當中,html
並不是一次寫成,而是經過不斷調整才成為上面這個樣子。
網格最終效果是個菱形,也就是正方形旋轉了45deg
,我們先不旋轉,看看它是什麼樣子
先設定一個變數,表示繩子的寬度,我們設為--width
,這個尺寸很重要
,後面所有尺寸都是基於這個寬度,這樣後面我們調整整個圖形的大小,只要改這一個--width
就行了。
:root { --width: 1.7vh; }
垂直和水平都各有11
根繩,繩子之間的間隙約為繩子寬度的 0.5
倍,所以可以得到網格的寬高都為 11 + 0.5 * 10 = 16
倍的繩子寬度,所以我們可以這樣寫:
:root { --width: 1.7vh; --grid-width: calc(var(--width) * 16); } .grid { width: var(--grid-width); height: var(--grid-width); }
給 body
加上一些樣式,讓盒子居中,再加一個深色背景
body{ margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #1d1e22; overflow: hidden; }
再給 .grid
也加一個白色背景色,測試一下:
這樣螢幕正中間就出現了一個白色方塊,下面我們把白色背景改成11
根線的樣式:
:root{ --width: 1.7vh; --red-1: #f40001; --red-2: #d40000; --red-3: #8c0703; --rope: var(--red-3), var(--red-2) calc(var(--width) * 0.25), var(--red-1) calc(var(--width) * 0.45), var(--red-1) calc(var(--width) * 0.55), var(--red-2) calc(var(--width) * 0.75), var(--red-3) var(--width); --grid-width: calc(var(--width) * 16); --bg-line: linear-gradient(90deg, var(--rope), transparent var(--width)) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5); } .grid{ width: var(--grid-width); height: var(--grid-width); background: var(--bg-line); }
就得到了下面的效果:
可能你有點蒙圈。發生了什麼事情?
還是讓事情變得簡單點,我們先畫一根不帶漸變的紅線:
.grid{ background: linear-gradient( 90deg, var(--red-1), var(--red-1) var(--width), transparent var(--width) ); }
先是一個線性漸變 linear-gradient
,然後旋轉角度設為90deg
,讓它從左到右漸變(預設是從下往上),然後起始值設為--red-1
(你問我red-1
到red-3
哪來的?效果圖上吸來的),在--width
處也設定為--red-1
,這樣就得到了一根寬為 --width
的紅線。但這還沒完,得接著在--width
處加一個透明transpanrent
,這樣從--width
直到圖形的最右側就都不填充顏色了。
但這不太像根繩子,讓紅線漸變起來:
.grid{ background: linear-gradient( 90deg, var(--red-3), var(--red-2) calc(var(--width) * 0.25), var(--red-1) calc(var(--width) * 0.45), var(--red-1) calc(var(--width) * 0.55), var(--red-2) calc(var(--width) * 0.75), var(--red-3) var(--width), transparent var(--width) ); }
這樣就得到了一根有一點點立體效果的繩子。可是怎麼讓它橫向重複11
次,並且間隔0.5
倍的--width
呢?看下面的程式碼:
.grid{ background: linear-gradient( 90deg, var(--red-3), var(--red-2) calc(var(--width) * 0.25), var(--red-1) calc(var(--width) * 0.45), var(--red-1) calc(var(--width) * 0.55), var(--red-2) calc(var(--width) * 0.75), var(--red-3) var(--width), transparent var(--width) ) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5); }
大家來找茬:這段程式碼和上一段有什麼不同?眼尖的你可能已經看出來了,多了這一行:
0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5)
以/
為分界線,左邊的含義是background-positoin
,右邊的含義是background-size
。
0 0
也就是左上角。calc(var(--width) * 1.5) calc(var(--width) * 1.5)
也就是一個正方形,寬度為1.5
倍繩寬。
這樣一個小方塊,在垂直和水平方向上重複,就得了我們想要的結果:
可是我們想要的是網格,現在頂多也就算個柵格。
那就使用偽類複製一份,並且旋轉90deg
:
:root{ --width: 1.7vh; --red-1: #f40001; --red-2: #d40000; --red-3: #8c0703; --rope: var(--red-3), var(--red-2) calc(var(--width) * 0.25), var(--red-1) calc(var(--width) * 0.45), var(--red-1) calc(var(--width) * 0.55), var(--red-2) calc(var(--width) * 0.75), var(--red-3) var(--width); --grid-width: calc(var(--width) * 16); --bg-line: linear-gradient(90deg, var(--rope), transparent var(--width)) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5); } .grid { width: var(--grid-width); height: var(--grid-width); background: var(--bg-line); &:after { content: ""; display: block; width: var(--grid-width); height: var(--grid-width); background: var(--bg-line); transform: rotate(90deg); } }
對比一下參考圖片:
不能說完全不相干,但是人家一看就經過了能工巧匠的編織,咱們這隻能算簡單的疊加,怎麼才能讓上面變成下面呢?
經過仔細的觀察,發現只要把上面一層橫著的線,稍加一些遮擋就能實現交叉編織的效果。用哪個css屬性實現呢?那就只有mask
了。
下圖藍色框是需要遮擋的部分,綠色框是需要重複的部分。
仔細分析一下綠框的構成:
本質上是在一個3×3
的正方形上挖兩個1×1
的小洞,位置分別是0 0
和 1.5 1.5
。我們要如何畫這樣一張圖?並把這張圖應用到mask
上呢?
mask
是通過傳入的圖片進行遮罩處理,而背景圖除了傳入一張png
以外,CSS還內建了幾個生成背景圖的函數:
linear-gradient
:線性漸變repeating-linear-gradient
:重複線性漸變radial-gradient
:徑向漸變conic-gradient
:圓錐漸變這些函數都可以和mask
配合。這裡我們使用conic-gradient
實現上面的圖形。
用conic-gradient
實現上圖,思路要反著來:不是在方形上挖孔,而是用多個矩形將要渲染的部分填充顏色,剩下的部分自然就是透明的:
CSS實現如下:
:root{ ... --conic: #000 0 90deg, transparent 0 100%; } .grid { ... &:after { ... -webkit-mask: conic-gradient(from 0deg at var(--width) calc(var(--width) * 1.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3), conic-gradient(from 90deg at calc(var(--width) * 2.5) 0, var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3), conic-gradient(from 180deg at calc(var(--width) * 1.5) var(--width), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3), conic-gradient(from 90deg at 0 calc(var(--width) * 2.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3); } }
預覽效果
目前為止完整程式碼
:root{ --width: 1.7vh; --red-1: #f40001; --red-2: #d40000; --red-3: #8c0703; --rope: var(--red-3), var(--red-2) calc(var(--width) * 0.25), var(--red-1) calc(var(--width) * 0.45), var(--red-1) calc(var(--width) * 0.55), var(--red-2) calc(var(--width) * 0.75), var(--red-3) var(--width); --grid-width: calc(var(--width) * 16); --bg-line: linear-gradient(90deg, var(--rope), transparent var(--width)) 0 0 / calc(var(--width) * 1.5) calc(var(--width) * 1.5); --conic: #000 0 90deg, transparent 0 100%; } body{ margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #1d1e22; overflow: hidden; } .grid { width: var(--grid-width); height: var(--grid-width); background: var(--bg-line); &:after { content: ""; display: block; width: var(--grid-width); height: var(--grid-width); background: var(--bg-line); transform: rotate(90deg); -webkit-mask: conic-gradient(from 0deg at var(--width) calc(var(--width) * 1.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3), conic-gradient(from 90deg at calc(var(--width) * 2.5) 0, var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3), conic-gradient(from 180deg at calc(var(--width) * 1.5) var(--width), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3), conic-gradient(from 90deg at 0 calc(var(--width) * 2.5), var(--conic)) 0 0 / calc(var(--width) * 3) calc(var(--width) * 3); } }
<div class="grid"></div>
沒錯,這個圖形,只用了.grid
這一個標籤!
但是隻有網格還不夠,讓我們繼續。
回頭看一下參考圖:
嗯,環形漸變,那就是radial-gradient
了:
<div class="ring-small"> <i></i> </div>
.ring-small { i { position: absolute; width: calc(var(--width) * 2.5); height: calc(var(--width) * 1.5); background: radial-gradient( circle at 50% 100%, transparent calc(var(--width) * 0.25), var(--red-3) calc(var(--width) * 0.25), var(--red-2) calc(var(--width) * (0.25 + 0.25)), var(--red-1) calc(var(--width) * (0.25 + 0.45)), var(--red-1) calc(var(--width) * (0.25 + 0.55)), var(--red-2) calc(var(--width) * (0.25 + 0.75)), var(--red-3) calc(var(--width) * (0.25 + 1)), transparent calc(var(--width) * (0.25 + 1)) ); } }
這樣就得到了半個環形圖,讓我們使用定位把它和網格結合看看
/* 先給最外層加個相對定位,後面的絕對定位都相對這一層 */ .chinese-knot { width: var(--grid-width); height: var(--grid-width); position: relative; } .ring-small { i { position: absolute; top: calc(var(--width) * -1.5); left: calc(var(--width) * 3); } }
對比素材圖,發現環形不是直接緊貼在網格上的,而是先延伸了一小段直線,再接的曲線。那我們就給它增個高吧:
.ring-small { i { &:before, &:after { content: ""; position: absolute; bottom: calc(var(--width) * -0.5 + 1px); width: var(--width); height: calc(var(--width) * 0.5); background: var(--bg-line); } &:after { right: 0; } } }
上面使用兩個偽類,為半圓環加了兩截高度為 0.5
倍 --width
的增高墊,效果如下圖
接著複製16
個這樣的圖形,分別定位到各自的位置上:
<div class="chinese-knot"> <div class="grid"></div> <div class="ring-small"> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> </div> </div>
.ring-small { i { position: absolute; width: calc(var(--width) * 2.5); height: calc(var(--width) * 1.5); background: radial-gradient( circle at 50% 100%, transparent calc(var(--width) * 0.25), var(--red-3) calc(var(--width) * 0.25), var(--red-2) calc(var(--width) * (0.25 + 0.25)), var(--red-1) calc(var(--width) * (0.25 + 0.45)), var(--red-1) calc(var(--width) * (0.25 + 0.55)), var(--red-2) calc(var(--width) * (0.25 + 0.75)), var(--red-3) calc(var(--width) * (0.25 + 1)), transparent calc(var(--width) * (0.25 + 1)) ); &:before, &:after { content: ""; position: absolute; bottom: calc(var(--width) * -0.5 + 1px); width: var(--width); height: calc(var(--width) * 0.5); background: var(--bg-line); } &:after { right: 0; } &:nth-child(-n + 4) { top: calc(var(--width) * -2 + 2px); } &:nth-child(1) { left: calc(var(--width) * 3); } &:nth-child(2) { left: calc(var(--width) * 6); } &:nth-child(3) { left: calc(var(--width) * 9); } &:nth-child(4) { left: calc(var(--width) * 12); } &:nth-child(-n + 8):nth-child(n + 5) { bottom: calc(var(--width) * -2 + 2px); transform: rotate(180deg); } &:nth-child(5) { left: calc(var(--width) * 1.5); } &:nth-child(6) { left: calc(var(--width) * 4.5); } &:nth-child(7) { left: calc(var(--width) * 7.5); } &:nth-child(8) { left: calc(var(--width) * 10.5); } &:nth-child(-n + 12):nth-child(n + 9) { left: calc(var(--width) * -2.5 + 2px); transform: rotate(-90deg); } &:nth-child(9) { top: calc(var(--width) * 3.5); } &:nth-child(10) { top: calc(var(--width) * 6.5); } &:nth-child(11) { top: calc(var(--width) * 9.5); } &:nth-child(12) { top: calc(var(--width) * 12.5); } &:nth-child(-n + 16):nth-child(n + 13) { right: calc(var(--width) * -2.5 + 2px); transform: rotate(90deg); } &:nth-child(13) { top: calc(var(--width) * 2); } &:nth-child(14) { top: calc(var(--width) * 5); } &:nth-child(15) { top: calc(var(--width) * 8); } &:nth-child(16) { top: calc(var(--width) * 11); } } }
就得到了這樣的效果
哈哈,很像下水管道~
還是先看素材:
嗯,不得不懷疑網易雲的 LOGO 的靈感是不是就是中國結。
單個環形已經實現了,兩個環也不難吧:
<div class="ring-big"> <i><b></b></i> </div>
.ring-big { i { position: absolute; width: calc(var(--width) * 6); height: calc(var(--width) * 6); b { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: radial-gradient( circle at 50% 50%, transparent calc(var(--width) * 0.5), var(--red-3) calc(var(--width) * 0.5), var(--red-2) calc(var(--width) * (0.5 + 0.25)), var(--red-1) calc(var(--width) * (0.5 + 0.45)), var(--red-1) calc(var(--width) * (0.5 + 0.55)), var(--red-2) calc(var(--width) * (0.5 + 0.75)), var(--red-3) calc(var(--width) * (0.5 + 1)), transparent calc(var(--width) * (0.5 + 1)), transparent calc(var(--width) * 2), var(--red-3) calc(var(--width) * 2), var(--red-2) calc(var(--width) * (2 + 0.25)), var(--red-1) calc(var(--width) * (2 + 0.45)), var(--red-1) calc(var(--width) * (2 + 0.55)), var(--red-2) calc(var(--width) * (2 + 0.75)), var(--red-3) calc(var(--width) * (2 + 1)), transparent calc(var(--width) * (2 + 1)) ); } } }
為什麼 <i>
標籤裡要再套一個標籤呢,因為接下來我們要執行 clip-path
,還要給圓環增高,而clip-path
會給增高的部分也裁剪掉,所以只能再套一層,讓內層的 <b>
自己 clip
,增高則使用 <i>
的偽類實現。下面就是將圓環右下角 1/4
裁剪掉並且加一個增高墊的程式碼:
.ring-big { i { ... b { ... clip-path: polygon(0 0, 100% 0, 100% 50%, 50% 50%, 50% 100%, 0 100%); } &:before, &:after { content: ""; position: absolute; top: calc(var(--width) * 3 - 1px); left: calc(var(--width) * 3.5); width: calc(var(--width) * 2.5); height: calc(var(--width) * 0.5 + 2px); background: repeating-linear-gradient( 90deg, var(--red-3), var(--red-2) calc(var(--width) * 0.25), var(--red-1) calc(var(--width) * 0.45), var(--red-1) calc(var(--width) * 0.55), var(--red-2) calc(var(--width) * 0.75), var(--red-3) var(--width), transparent var(--width), transparent calc(var(--width) * 1.5) ); } &:after { transform: rotate(90deg); transform-origin: left top; top: calc(var(--width) * 3.5); left: calc(var(--width) * 3.5 + 1px); } } }
複製一份並定位:
.ring-big { i { ... &:nth-child(1) { left: calc(var(--width) * -3.5); top: calc(var(--width) * -3.5); } &:nth-child(2) { left: auto; top: auto; right: calc(var(--width) * -3.5); bottom: calc(var(--width) * -3.5); transform: rotate(180deg); } } }
到這裡,工作的一半就已經完成了~繼續
這個圖形,相對於上面幾個,已經沒什麼難度了,五個1×1
的正方形,中間的漸變方向和周圍四個垂直。
中間的正方形,用父級本身實現,裡面周圍四個,用四個子<i>
標籤實現:
<div class="cross-node"> <div class="node"> <i></i> <i></i> <i></i> <i></i> </div> <div class="node"> <i></i> <i></i> <i></i> <i></i> </div> </div>
.cross-node { .node { position: absolute; z-index: 2; width: var(--width); height: var(--width); background: var(--bg-line); i { position: absolute; width: var(--width); height: var(--width); background: var(--bg-line); transform: rotate(90deg); &:nth-child(1) { left: calc(var(--width) * -1); } &:nth-child(2) { left: var(--width); } &:nth-child(3) { top: calc(var(--width) * -1); } &:nth-child(4) { top: var(--width); } } &:nth-child(1) { right: calc(var(--width) * -1); top: calc(var(--width) * -1); } &:nth-child(2) { left: calc(var(--width) * -1); bottom: calc(var(--width) * -1); } } }
前面我們都是讓中國結處於一個斜躺的姿態,寫頭部和尾部之前,讓我們先把它擺正:
.chinese-knot { ... transform: rotate(-45deg) translate(calc(var(--width) * 4), calc(var(--width) * -4)); }
回頭看素材圖:
先確定一下html
結構:
<div class="header"> <i></i> <b></b> <span></span> </div>
i
是上面的吊繩,b
是圓環,span
是銜接處的短繩,帶點黃色裝飾。為了方便調整定位,我們從下往上實現,先寫短繩:
:root { --yellow-1: #fced00; --yellow-2: #f28a00; --yellow-3: #da571b; --bg-yellow: linear-gradient( 90deg, var(--yellow-3), var(--yellow-2) 20%, var(--yellow-1) 40%, var(--yellow-1) 60%, var(--yellow-2) 80%, var(--yellow-3) 100% ); } .header { position: absolute; right: 0; top: 0; transform: rotate(45deg); i { position: absolute; bottom: calc(var(--width) * 1); left: calc(var(--width) * -0.5); width: calc(var(--width) * 1); height: calc(var(--width) * 2); background: var(--bg-line); &:before { content: ""; display: block; height: calc(var(--width) * 0.5); background: var(--bg-yellow); } } }
然後是圓環:
.header { ... b { position: absolute; bottom: calc(var(--width) * 3); left: calc(var(--width) * -1.5); width: calc(var(--width) * 3); height: calc(var(--width) * 3); background: radial-gradient( circle at 50%, transparent calc(var(--width) * 0.75), var(--red-3) calc(var(--width) * 0.75), var(--red-2) calc(var(--width) * (0.75 + 0.15)), var(--red-1) calc(var(--width) * (0.75 + 0.3)), var(--red-1) calc(var(--width) * (0.75 + 0.45)), var(--red-2) calc(var(--width) * (0.75 + 0.6)), var(--red-3) calc(var(--width) * (0.75 + 0.75)), transparent calc(var(--width) * (0.75 + 0.75)) ); } }
最後是長的吊繩:
.header { ... span { position: absolute; bottom: calc(var(--width) * 5); left: calc(var(--width) * -0.25); width: calc(var(--width) * 0.5); height: calc(var(--width) * 30); background: linear-gradient(90deg, var(--red-2), var(--red-1) 20%, var(--red-2) 70%, var(--red-3)); border-radius: calc(var(--width) * 0.25); } }
單獨效果
整體效果
確定html
結構:
<div class="footer"> <b></b> <b></b> <div class="tassels"> <i></i> <i></i> </div> </div>
可以看到,流蘇部分,有兩個彎曲的 1/8
環,我們用兩個b
標籤來表示。這個形狀依然還是先畫一個完整的環,然後裁剪來實現:
.footer { position: absolute; left: 0; bottom: 0; b { position: absolute; width: calc(var(--width) * 15); height: calc(var(--width) * 15); background: radial-gradient( circle at 50%, transparent calc(var(--width) * 6.5), var(--red-3) calc(var(--width) * 6.5), var(--red-2) calc(var(--width) * (6.5 + 0.25)), var(--red-1) calc(var(--width) * (6.5 + 0.45)), var(--red-1) calc(var(--width) * (6.5 + 0.55)), var(--red-2) calc(var(--width) * (6.5 + 0.75)), var(--red-3) calc(var(--width) * (6.5 + 1)), transparent calc(var(--width) * (6.5 + 1)) ); } }
加上裁剪並定位:
.footer { ... b { ... &:nth-child(1) { left: calc(var(--width) * -8.5); top: calc(var(--width) * 1); clip-path: polygon(50% 0, 50% 50%, 10% 0); } &:nth-child(2) { left: calc(var(--width) * -16); top: calc(var(--width) * -6.5); clip-path: polygon(100% 50%, 50% 50%, 100% 90%); } } }
兩個小尾巴就實現了。
最後是流蘇。先畫一下背景上的垂直細線,這裡我們用 repeating-linear-gradient
實現,每隔 2px
畫一條 1px
寬的透明度為 0.2
的黑線:
.footer { .tassels { i { position: absolute; width: calc(var(--width) * 2.5); height: calc(var(--width) * 14); background: var(--red-2) repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 3px) 50% 50% / 3px 1px;} } }
再蒙上一層黃色的裝飾:
.footer { .tassels { i { ... &:before { content: ""; position: absolute; top: calc(var(--width) * 0.5); width: 100%; height: calc(var(--width) * 3.6); background: var(--bg-yellow); clip-path: polygon(0 0, 100% 0, 100% 10%, 0 10%, 0 15%, 100% 15%, 100% 85%, 0 85%, 0 90%, 100% 90%, 100% 100%, 0 100%, 0 0); } } }
上面程式碼中使用 clip-path
對黃色背景裁剪,露出兩條紅線,裁剪路徑可以用下圖表示:
最終效果:
本來到這裡就應該結束了。但是我想讓這個中國結有點實際用途,比如加點互動什麼的。
紅包也是春節的習俗之一,那就加一個拉一下中國結掉落紅包雨的特效吧~
給中國結在:active
狀態下加個位移即可實現:
.chinese-knot { width: var(--grid-width); height: var(--grid-width); position: relative; transform: rotate(-45deg) translate(calc(var(--width) * 4), calc(var(--width) * -4)); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: all 0.5s; &:active { transform: rotate(-45deg) translate(calc(var(--width) * 2), calc(var(--width) * -2)); } }
先搜尋一個紅包素材:
觀察一下紅包結構,深紅背景,淺紅弧形開口,加一個黃色圓形封口,上面寫著一個繁體的開字。
我們可以先確定 html
結構。.rain
作為外層,代表整個紅包雨,一個i
標籤代表一個紅包:
<div class="rain"> <i></i> </div>
一個標籤怎麼實現上面提到的三種元素呢?看程式碼:
.rain { position: absolute; top: 0; left: 0; right: 0; display: flex; justify-content: space-around; i { position: relative; display: block; width: calc(var(--width) * 5); height: calc(var(--width) * 8); background: var(--red-3); border-radius: calc(var(--width) * 0.4); overflow: hidden; box-shadow: 0 calc(var(--width) * 1) calc(var(--width) * 1) rgba(0, 0, 0, 0.3); &:before { content: ""; position: absolute; left: 50%; transform: translate(-50%, -50%); width: calc(var(--width) * 8); height: calc(var(--width) * 8); background: var(--red-1); opacity: 0.5; border-radius: 50%; } &:after { content: "開"; position: absolute; left: 50%; transform: translate(-50%, 140%); width: calc(var(--width) * 2); height: calc(var(--width) * 2); background: var(--yellow-2); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-style: normal; font-size: calc(var(--width) * 0.5); color: var(--yellow-1); text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.1); } } }
使用i
標籤自身實現紅包主體,:before
偽類實現弧形的開口,:after
偽類實現黃色圓形封口,在content
中寫上開
字。
一個紅包完成了,再複製 9 個:
<div class="rain"> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> <i></i> </div>
這樣就得到了 10 個固定在頂部,並且整齊排列的紅包了。
下雨嘛,從上往下運動就好了:
.rain { ... i { ... animation: fall 3s ease-in infinite; } } @keyframes fall { 0% { transform: translate(0, 0); } 100% { transform: translate(0, 100vh); } }
聰明的你估計已經猜到了這樣的結果:誰家的雨是這樣齊刷刷的下來的?
那我們就紅包的垂直位置錯落一點,使用 sass
的 random
函數來實現隨機:
.rain { ... i { ... @for $i from 1 through 10 { &:nth-child(#{$i}) { top: random(60) + vh; } } } }
額,效果怎麼和想象的不一樣。依舊還是齊刷刷下落,只不過是"錯落"的齊刷刷。
那我們讓每個紅包的開始時間也隨機不就行了嘛:
.rain { ... i { ... @for $i from 1 through 10 { &:nth-child(#{$i}) { top: random(60) + vh; animation-delay: random(30) * 0.1s; } } } }
嗯,好了一點點。但是有一個問題,螢幕上的雨點,有時候很多,有時候很少,不夠均勻。那我們把動畫的持續時間也隨機會怎麼樣呢?
.rain { ... i { ... @for $i from 1 through 10 { &:nth-child(#{$i}) { top: random(60) + vh; animation-delay: random(30) * 0.1s; animation-duration: random(10) * 0.1s + 2s; /* 2s ~ 3s 之間隨機 */ } } } }
終於更像雨了~
但是現在雨滴是憑空出現的,很生硬,我們只要把開始的位置挪到負一屏
,然後讓它下落到正二屏
就行了:
.rain { ... top: -100vh; } @keyframes fall { 0% { transform: translate(0, 0); } 100% { transform: translate(0, 200vh); } }
這樣就有了源源不斷下落的效果。
CSS
不是 JS
,怎麼觸發點選事件呢?
我們就要運用 CSS
本身的特性了,checkbox
核取方塊有個選中狀態 :checked
,而核取方塊可以用點選切換這個狀態,再使用 CSS
的兄弟選擇器 element ~ element
即可實現點選新增樣式的效果。
樣式可以觸發了,那如何觸發動畫呢?
animation
屬性新增到元素上後,播放狀態預設是 running
,我們需要先把初始播放狀態改為 paused
(暫停), 然後通過上面的方法,把元素的播放狀態改回 running
來實現播放動畫的效果:
<input type="checkbox" id="switch"> <label class="chinese-knot" for="switch">...</label> <div class="rain">...</div>
.rain { ... i { ... animation: fall 3s ease-in infinite; /* 預設不播放動畫 */ animation-play-state: paused; } } #switch { visibility: hidden; pointer-events: none; } /* checkbox 選中時播放動畫 */ #switch:checked ~ .rain i { animation-play-state: running; } /* 點選時重置動畫,否則取消checkbox選中狀態,動畫會中止並停留在當前位置 */ .chinese-knot:active ~ .rain i { animation: none; }
上面的 html
中,我們讓.chinese-knot
從 div
改為 label
來指向 checkbox
,方法是 label
的 for
和 checkbox
的 id
設為相同的值。
效果很不錯,我們再給紅包雨下落時加個背景,以提醒使用者當前的狀態。並且下紅包雨時,調低中國結的透明度,以突出紅包的存在感。
<input type="checkbox" id="switch"> <div class="bg"></div> <label class="chinese-knot" for="switch">...</label> <div class="rain">...</div>
.bg { position: absolute; left: 0; top: 0; height: 100vh; width: 100vw; background: linear-gradient(0deg, #171a4b, #96367f); opacity: 0; transition: all 0.5s; } #switch:checked ~ .bg { opacity: 1; } #switch:checked ~ .chinese-knot { opacity: 0.2; &:hover { opacity: 0.5; } }
完結撒花~~~
這篇文章整理了我從蒐集素材開始,創作一個作品的全部過程,程式碼寫了一天,這篇文章寫了半天。希望能讓 CSS 初學者對 CSS 燃起興趣,也希望讓接觸了一段時間 CSS 的朋友獲得一些靈感和幫助。
(學習視訊分享:)
以上就是手把手帶你使用純CSS繪製一箇中國結,並新增動畫效果!的詳細內容,更多請關注TW511.COM其它相關文章!