某日,群裡有這樣一個問題,如何實現這樣的錶盤刻度:
這其實是個挺有意思的問題,方法也有很多。
單標籤,使用 conic-gradient 實現錶盤刻度
最簡單便捷的方式,就是利用角向漸變的方式 conic-gradient
,程式碼也非常簡單,首先,我們實現一個重複角向漸變:
<div></div>
div {
width: 300px;
height: 300px;
border-radius: 50%;
background: repeating-conic-gradient(
#000 0, #000 .8deg, transparent 1deg, transparent calc((360 / 60) * 1deg)
);
}
其實比較難理解的是 calc((360 / 60) * 1deg)
,這是因為錶盤一共通常有 60 個刻度。效果大概是這樣:
接下來,只需要將中間鏤空即可。如果背景色是白色,直接疊加一個圓形即可,當然,更好的方式是通過 mask 屬性進行鏤空:
{
background: repeating-conic-gradient(
#000 0, #000 .8deg, transparent 1deg, transparent calc(360 / 60 * 1deg)
);
mask: radial-gradient(transparent 0, transparent 140px, #000 140px)
}
這樣,我們就得到了一個錶盤刻度:
這是使用一個標籤就能實現的方式,當然,缺點也很明顯:
- 鋸齒感嚴重,漸變的通病
- 由於是使用的角向漸變,刻度存在頭重腳輕的現象,越向內部,寬度越窄(刻度愈大,差異愈加明顯)
使用多個標籤實現
如果不介意使用太多的標籤,那麼通常而言,更容易想到的方法就是利用 60 個標籤,加上旋轉實現:
<div class="g-container">
<div class="g-item"></div>
// ... 一共 60 個
<div class="g-item"></div>
</div>
.g-item {
position: absolute;
width: 4px;
height: 12px;
background: #000;
left: 0;
top: 0;
transform-origin: 0 150px;
}
@for $i from 1 through 60 {
.g-item:nth-child(#{$i}) {
transform: rotate(#{($i - 1) * 6deg});
}
}
像是這樣,我們通過 60 個 div 標籤,利用 SASS 的 for 語法減少重複的程式碼量,批量實現每個元素逐漸繞一點旋轉一定的角度,也是可以實現一個錶盤刻度的:
這個方案的好處是,每個刻度粗細一致,並且,不會產生鋸齒。
藉助 -webkit-box-reflect 減少標籤數
當然,上述方案的缺點就在於,使用了 60 個標籤去完成這樣一個簡單的圖形,有點太奢侈了。
我們希望儘可能的優化優化標籤的個數。此時,我們很容易的想到 -- -webkit-box-reflect
,倒影效果屬性。
-webkit-box-reflect
是一個非常有意思的屬性,它讓 CSS 有能力像鏡子一樣,反射我們元素原本繪製的內容。
-webkit-box-reflect
的語法非常簡單,最基本的用法像是這樣:
div {
-webkit-box-reflect: below;
}
其中,below 可以是 below | above | left | right 代表下上左右,也就是有 4 個方向可以選。
假設我們有如下一張圖片:
<div></div>
div {
background-image: url('https://images.pokemontcg.io/xy2/12_hires.png');
}
加上 -webkit-box-reflect: right
,也就是右側的倒影:
div {
background-image: url('https://images.pokemontcg.io/xy2/12_hires.png');
-webkit-box-reflect: right;
}
效果如下,生成了一個元素右側的映象元素:
藉助 -webkit-box-reflect: right
,我們至少可以從 60 個標籤減少到 15 個標籤的使用。簡單的巢狀兩層即可。
我們簡單改變一下 HTML 結構:
<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
// ... 一共 16 個
<div class="g-item"></div>
</div>
</div>
這一次,我們只需要實現 1/4 圓的刻度即可:
@for $i from 1 through 16 {
.g-item:nth-child(#{$i}) {
transform: rotate(#{($i - 1) * 6deg});
}
}
我們可以得到這樣一個圖形:
基於這個圖形,我們只需要先向左側倒影一次,再向下倒影一次即可:
.g-container {
-webkit-box-reflect: below;
}
.g-parent {
-webkit-box-reflect: left;
}
效果如下:
大致的效果就出來了,當然,0點、3點、6點、9點上左下右 4 個刻度有點問題。不過 -webkit-box-reflect
也提供距離調整功能,再簡單修改下程式碼:
.g-container {
-webkit-box-reflect: below 4px;
}
.g-parent {
-webkit-box-reflect: left -4px;
}
這次,效果就是我們想要的最終效果:
我們就成功地藉助 -webkit-box-reflect
節約了 3/4 的標籤數量。完整的程式碼:CodePen Demo -- Clock ticks
-webkit-box-reflect 與剪紙藝術
到這裡,我不由得想到,這種對摺、再對摺,映象再映象的方式,與我們小時候的摺紙藝術非常的類似。
那麼,基於這樣一個模板:
<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
</div>
</div>
.g-container {
-webkit-box-reflect: below;
}
.g-parent {
-webkit-box-reflect: left;
}
我只需要繪製 .g-item
裡面的內容,將他通過 2 次 -webkit-box-reflect
映象,就能得到一個剪紙圖形。
而如何得到隨機有意思的不規則圖形呢?
clip-path
是個很不錯的選擇,我們通過 clip-path
隨機對一個矩形進行裁剪:
.g-item {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(25% 0%,71% 66%,59% 0%,79% 23%,95% 4%,100% 40%,77% 100%,38% 100%,47% 71%,36% 30%,23% 60%,0% 100%,5% 37%);
}
效果如下:
經過兩次映象後的效果如下:
是不是有那麼點意思了?可以隨機利用 clip-path
多嘗試幾次,可以得到不同的效果:
CodePen Demo -- Pure CSS Page Cutting
-webkit-box-reflect 配合 clip-path 配合 mask
但是上面的圖形看著還是太簡單了,幾個原因,一是對摺的次數和角度不夠,缺少對摺次數和不同角度的對摺,二是圖形不夠負責。
我又想起了之前看到過的一篇類似的剪紙相關的文章 -- Paper Snowflakes: Combining Clipping and Masking in CSS。
再上述的基礎上,還使用了 mask,將圖形切割的更細。
我們再來一次,還是同樣的結構,當然,為了得到更負責的圖形,我們設定了 4 個 .g-item
:
<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
</div>
</div>
首先,還是設定一個 clip-path
切割後的圖形:
.g-item:nth-child(1) {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(17% 41%,6% 39%,16% 91%,18% 78%,56% 11%,28% 71%,99% 67%,25% 65%,69% 72%,46% 28%,90% 76%,67% 34%,48% 30%,79% 36%,59% 15%,23% 92%,16% 1%,32% 81%,72% 38%,50% 59%,71% 98%,66% 87%,83% 14%,36% 71%,49% 7%,9% 25%,52% 76%,10% 83%,17% 41%);
}
效果如下:
這個圖可能它會特別的奇怪,沒有問題,我們繼續。
如果,我們將一個矩形,從左下角開始算,分為 4 份,那麼每一個份就是 90° / 4 = 22.5°
,我們希望切割得到其中一份:
我們可以藉助 mask 去完成這個切割:
.g-item:nth-child(1) {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(.....);
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
}
上述的圖形,就被切割成了這樣:
OK,基於此,我們可以得到同樣的第二份圖形,但是我們給它加一個 rotateY(180deg)
:
.g-item:nth-child(2) {
width: 150px;
height: 150px;
background: #000;
clip-path: polygon(.....);
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
transform: rotateY(180deg);
}
效果如下:
我們再通過 rotateZ()
,給第二圖形旋轉一定的角度,讓他和第一個貼合在一起:
.g-item:nth-child(2) {
clip-path: polygon(.....);
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
transform: rotateY(180deg) rotateZ(-45deg);
}
就得到了一個斜向角度的映象影像:
因為 .g-item
被切割成了 4 份,所以第 3、4 個圖形如法炮製即可,這樣,整個 .g-item
的效果如下:
再開啟 -webkit-box-reflect
,整個的圖形效果如下:
這樣,一個剪紙圖形就誕生啦!
當然,為了得到不一樣的效果,我們可以藉助 JavaScript 去隨機生成 CSS 中的各類引數,完整的程式碼,大概是這樣:
<div class="g-parent">
<div class="g-container">
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
<div class="g-item"></div>
</div>
</div>
.g-container,
.g-parent {
position: relative;
display: flex;
width: 150px;
height: 150px;
}
.g-item {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: #000;
transform-origin: 0 100%;
clip-path: var(--polygon, polygon(40% 0%,0% 91%,52% 100%,0% 37%,77% 23%,77% 76%,43% 22%,55% 88%,100% 100%,100% 10%));
}
.g-item {
mask: conic-gradient(from 0turn at 0 100%, #000, #000 22.5deg, transparent 22.5deg, transparent);
}
@for $i from 1 through 5 {
.g-item:nth-child(#{$i}) {
transform: rotateZ(calc(22.5deg * #{$i - 1}));
}
}
.g-item:nth-child(2) {
transform: rotateY(180deg) rotateZ(-60deg);
}
.g-item:nth-child(4) {
transform: rotateY(180deg) rotateZ(-105deg);
}
.g-container {
-webkit-box-reflect: below;
}
.g-parent {
-webkit-box-reflect: left;
}
const ele = document.querySelectorAll('.g-item');
document.addEventListener('click', function(e) {
let num = Math.floor(Math.random() * 30 + 10);
const maskR = Math.floor(Math.random() * 22.5 + 22.5 ) + 'deg';
const r1 = Math.floor(Math.random() * 100) + '%';
const r2 = Math.floor(Math.random() * 100) + '%';
let polygon = 'polygon(' + r1 + ' ' + r2 + ',';
for (let i=0; i<num; i++) {
const newR1 = Math.floor(Math.random() * 100) + '%';
const newR2 = Math.floor(Math.random() * 100) + '%';
polygon += newR1 + ' ' + newR2 + ','
}
polygon += r1 + ' ' + r2 + ')';
[...ele].forEach(item => {
item.setAttribute('style', `--polygon:${polygon};-webkit-mask:conic-gradient(from 0turn at 0 100%, #000, #000 ${maskR}, transparent ${maskR}, transparent)`);
});
});
這樣,每次點選滑鼠,我們都能得到不同的隨機剪紙圖案:
看看這個簡單錄製的 GIF:
完整的程式碼,你可以猛擊這裡 CodePen Demo -- Pure CSS Art Page Cutting
最後
本文到此結束,希望對你有幫助 :)
想 Get 到最有意思的 CSS 資訊,千萬不要錯過我的公眾號 -- iCSS前端趣聞 ?
更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。
如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。