今天,收到一個很有意思的提問,如何實現類似如下的背景效果圖:
嗯?核心主體是由多個六邊形網格疊加形成。
那麼我們該如何實現它呢?使用純 CSS 能夠實現嗎?
當然可以,下面我們就將嘗試如何使用 CSS 去實現這樣一個背景效果。
如何繪製六邊形?
首先,看到這樣一個圖形,如果想要使用一個標籤完成整個背景,最先想到的肯定是使用背景 background 實現,不過可惜的是,儘管 CSS 中的 background 非常之強大,但是沒有特別好的方式讓它足以批次生成重複的六邊形背景。
因此,在這個需求中,我們可能不得不退而求其次,一個六邊形實現使用一個標籤完成。
那麼,就拿 1 個 DIV 來說,我們有多少實現六邊形的方式呢?這裡簡單介紹 2 種方式:
- 使用 border 實現六邊形
- 使用 clip-path 實現六邊形
使用 border 或者 clip-path 實現六邊形
首先,使用 border 實現六邊形。這裡的核心在於上下兩個三角形疊加中間一個矩形。這裡,利用元素的兩個偽元素實現上下兩個三角形,從而讓這個元素看起來像一個六邊形。
思路比較簡單,直接上程式碼:
.hexagon {
position: relative;
width: 200px;
height: 100px;
background-color: red;
}
.hexagon:before,
.hexagon:after {
content: "";
position: absolute;
width: 0;
height: 0;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
}
.hexagon:before {
bottom: 100%;
border-bottom: 50px solid red;
}
.hexagon:after {
top: 100%;
border-top: 50px solid red;
}
上面的程式碼會建立一個寬度為 200 畫素,高度為 100 畫素的六邊形,其中由兩個三角形和一個矩形組成。使用偽元素的優點是可以很方便地控制六邊形的大小、顏色等樣式。
當然,上述的程式碼不是一個正六邊形,這是因為正六邊形中,元素的高是元素的寬的 1.1547 倍。
並且,上述的方式也稍微複雜了點,因此,在今天,我們更推薦使用 clip-path
的方式去實現一個六邊形:
.clippath {
--w: 100px;
width: var(--w);
height: calc(var(--w) * 1.1547);
clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
background: deeppink;
margin: auto;
}
這樣,基於 clip-path
,也能快速得到一個六邊形圖形:
CodePen Demo -- Two ways to achieve a hexagon
繪製多個六邊形背景
好了,有了上一步的鋪墊之後,接下來我們要做的,就是繪製多個六邊形,組成背景。
但是我們仔細觀察一下由多個六邊形組成的背景,會發現每雙數行的的六邊形,需要向右側有一個明顯的縮排,寬度大概為單個六邊形的寬度的一半:
這裡其實是一個非常棘手的問題。首先,我們會想到這樣一種解決方案:
- 每一行為一組,設定一個父 div 容器,填滿六邊形元素,設定元素不換行
- 給偶數行設定一個固定的
margin-left
基於這個策略,我們的程式碼,大概會是這樣:
<div class="container">
<div class="wrap">
// ... 填滿六邊形
</div>
<div class="wrap" style="margin-left: 25px">
// ... 填滿六邊形
</div>
<div class="wrap">
// ... 填滿六邊形
</div>
<div class="wrap" style="margin-left: 25px">
// ... 填滿六邊形
</div>
</div>
可以看到,我們給偶數行,都新增了一個 margin-left
。
但是這個程式碼,會有幾個問題:
- 我們的頁面寬度不一定是固定的,那麼每一行設定多少個子六邊形元素比較合適呢?設定多了勢必會帶來浪費,少了又無法滿足需求
- 多了一層巢狀,程式碼邏輯更為複雜
什麼意思呢?也就是效果可能在螢幕非常寬的情況下,失效。
看看,正常情況,我們設定了每行 20 個六邊形,下圖是正常的
但是如果我們的螢幕特別寬,那麼,可能會得到這樣一種效果:
因此,這種方式存在非常大的弊端,我們希望能有一整佈局方式,能夠滿足我們如下兩個訴求:
- 所有六邊形程式碼寫在一個父容器下
- 這個彈性佈局中,第二行的元素最左邊,能夠實現固定一個縮排
仔細思考一下,CSS 中有能夠實現類似佈局的方法麼?
妙用 shape-outside 實現隔行錯位佈局
有的!在 CSS 中,有一個神奇的元素能夠讓元素以非直線形式排布。它就是 shape-outside
!
如果你對
shape-outside
不太瞭解,也可以先看看我的這篇文章 -- 奇妙的 CSS shapes
shape-outside
是 CSS 中的一個屬性,用於控制元素的浮動方式。它允許你定義一個元素浮動時周圍元素的形狀。例如,你可以使用 shape-outside
屬性來定義一個元素浮動時周圍元素的形狀為圓形、六邊形等。
它和 clip-path
的語法非常類似,很容易觸類旁通。看看例項,更易理解:
假設我們有下面這樣的結構存在:
<div class="container">
<div class="shape-outside">
<img src="image.png">
</div>
xxxxxxxxxxx,文字描述,xxxxxxxxx
</div>
定義如下 CSS:
.shape-outside {
width: 160px;
height: 160px;
shape-outside: circle(80px at 80px 80px);
float: left;
}
注意,上面 .shape-outside
使用了浮動,並且定義了 shape-outside: circle(80px at 80px 80px)
,表示在元素的 (80px, 80px) 座標處,生成一個 80px 半徑的圓。
如此,將會產生一種圖文混排的效果:
CodePen Demo -- 圖文混排 shape-outside
總得來說,shape-outside
有兩個核心特點:
shape-outside
屬性僅在元素定義了float
屬性且不為none
時才會生效- 它能夠實現了文字根據圖形的輪廓,在其周圍排列
shape-outside
的本質
劃重點,劃重點,劃重點。
所以,shape-outside
的本質其實是生成幾何圖形,並且裁剪掉其幾何圖形之外周圍的區域,讓內容能排列在這些被裁剪區域之內。
所以,瞭解了這個本質之後,我們再將他運用在上面的六邊形佈局之中。
為了方便理解,我們首先使用文字代替上面的六邊形,假設我們有這樣一段文字內容:
<p>
Lorem ipsum dolor sit amet conse...
</p>
p {
line-height: 36px;
font-size: 24px;
}
非常平平無奇的一段程式碼,效果如下:
現在,我們想利用 shape-outside
,讓文字內容的偶數行,向內縮排 24px
,怎麼實現呢?非常簡單:
p {
position: relative;
line-height: 36px;
font-size: 24px;
&::before {
content: "";
height: 100%;
width: 24px;
shape-outside: repeating-linear-gradient(
transparent 0,
transparent 36px,
#000 36px,
#000 72px
);
float: left;
}
}
這樣,我們就實現了文字隔行縮排 24px
的效果:
一定有小夥伴會很好奇,為什麼呢?核心在於我們利用元素的偽元素實現了一個 shape-outside
圖形,如果我們把這個圖形用 background
繪製出來,其實它長這樣:
p {
position: relative;
line-height: 36px;
font-size: 24px;
&::before {
content: "";
height: 100%;
width: 24px;
shape-outside: repeating-linear-gradient(
transparent 0,
transparent 36px,
#000 36px,
#000 72px
);
float: left;
background: repeating-linear-gradient(
transparent 0,
transparent 36px,
#f00 36px,
#f00 72px
);
}
}
效果如下:
因為文字的行高是 36px
,這樣我們以 72 為一段,每 36px 繪製一段透明,另外 36px 繪製一段寬為 24px 的內容,這樣,結合 shape-outside
的特性,我們就實現了隔行將內容向裡面擠 24px
的效果!
非常的 Amazing 的技巧!完整的程式碼你可以戳這裡:
CodePen Demo -- Shape-outside achieves even line indentation
基於這個技巧,我們就可以實現上述我們想要的效果了。我們回到正題,重新實現一個充滿六邊形的背景:
<ul class="wrap">
<li></li>
//... 非常多個 li
<ul>
:root {
--s: 50px; /* size */
--m: 4px; /* margin */
--perHeight: calc(calc(var(--s) * 2 * 1.1547) + calc(var(--m) * 4) - 0.4px)
}
.wrap {
position: relative;
height: 100%;
font-size: 0;
&::before {
content: "";
height: 100%;
width: 27px;
shape-outside: repeating-linear-gradient(
transparent 0,
transparent 70px,
#000 70px,
#000 var(--perHeight)
);
float: left;
}
}
li {
width: var(--s);
height: calc(var(--s) * 1.1547);
background: #000;
clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
margin: var(--m);
display: inline-block;
}
藉助 shape-outside
,我們就實現了隔行讓我們的六邊形向內縮排的訴求!效果如下:
當然,有一些最佳化點:
- 為了讓兩邊不那麼空,我們可以讓整個容器更寬一點,譬如寬度為父元素的
120%
,然後水平居中,這樣,兩側的留白就解決了 - 讓兩行直接貼緊,可以設定一個
margin-bottom
做完這兩點最佳化之後,效果如下:
可以做到任意螢幕寬度下的六邊形完美平鋪佈局:
完整的程式碼你可以戳這裡:CodePen Demo -- Hexagon Layout
配置上色彩變換
有了上述的鋪墊後,要實現文章一開頭的效果就不難了。
是的,我們要實現這樣一個效果:
如何讓它們動態的實現顏色變換呢?是給每一個六邊形一個單獨的顏色,然後進行動畫嗎?不,藉助混合模式,我們可以非常快速的實現不同的顏色值。
首先,我們將上述效果,改成白底黑色六邊形色塊:
然後,利用父容器剩餘的一個偽元素,我們疊加一層漸變層上去:
.wrap {
position: relative;
// 程式碼與上述保持一致
&::before {
content: "";
// ... 實現 shape-outside 功能,程式碼與上述保持一致
}
&::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(45deg, #f44336, #ff9800, #ffe607, #09d7c4, #1cbed3, #1d8ae2, #bc24d6);
}
}
這樣,我們就疊加了一層漸變色彩層在原本的六邊形背景之上:
接著,只需要一個混合模式 mix-blend-mode: darken
,就能實現六邊形色塊與上層漸變顏色的融合效果:
.wrap {
position: relative;
// 程式碼與上述保持一致
&::before {
content: "";
// ... 實現 shape-outside 功能,程式碼與上述保持一致
}
&::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(45deg, #f44336, #ff9800, #ffe607, #09d7c4, #1cbed3, #1d8ae2, #bc24d6);
z-index: 1;
+ mix-blend-mode: darken;
}
}
效果如下:
好, 我們再給上層的漸變色塊,新增一個 filter: hue-rotate()
動畫,實現色彩的漸變動畫:
.wrap {
position: relative;
// 程式碼與上述保持一致
&::before {
content: "";
// ... 實現 shape-outside 功能,程式碼與上述保持一致
}
&::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(45deg, #f44336, #ff9800, #ffe607, #09d7c4, #1cbed3, #1d8ae2, #bc24d6);
z-index: 1;
mix-blend-mode: darken;
+ animation: change 10s infinite linear;
}
}
@keyframes change {
100% {
filter: hue-rotate(360deg);
}
}
這樣,我們就完美的實現了我們想要的效果:
完整的程式碼,你可以戳這裡:CodePen Demo -- Hexagon Gradient Layout
擴充套件延伸
當然,有了這個基礎圖形之後,其實我們可以基於這個圖形,去做非常多有意思的效果。
下面我是嘗試的一些效果示意,譬如,我們可以將顏色放置在六邊形背景的下方,製作這樣一種效果:
CodePen Demo -- Hexagon Gradient Layout
配合 mask 的蒙版效果及滑鼠定位,我們還能實現這樣一種有趣的互動效果:
CodePen Demo -- Hexagon Gradient & MASK Layout
當然,3D 效果也不在話下:
CodePen Demo -- 3D Hexagon Gradient Layout
最後
好了,本文到此結束,希望本文對你有所幫助 ?
更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。
如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。