歡迎關注微信公眾號:前端偵探
練習 CSS 一個很好的方法就是繪製各式各樣的 UI,比如這樣一個時鐘?
你也可以訪問這個 CSS clock檢視實際效果
CSS 繪製這樣一個佈局有幾個難點:
- 環形排列的刻度
- 環形分佈的數字
- 自動執行的指標
下面就來一一實現它,相信能學到很多 CSS 繪製和動畫的小技巧
一、環形排列的刻度
提到“環形”,可以想到錐形漸變 conic-gradient。假設有這樣一個容器
<clock></clock>
加上一點錐形漸變
clock{
width: 300px;
height: 300px;
background: conic-gradient(#333 0 15deg, #cddc39 0deg 30deg);
}
可以得到這樣的效果
如何做出交錯相間的效果呢?可以試試 repeating-conic-gradient
clock{
/**/
background: repeating-conic-gradient(#333 0 15deg, #cddc39 0deg 30deg);
}
效果如下
還是看不出和刻度有啥關係?沒關係,我們把黑色部分的角度改小一點
clock{
/**/
background: repeating-conic-gradient(#333 0 1deg, #cddc39 0deg 30deg);
}
效果如下
這樣繪製出來的幾條線是不是剛好可以對應時鐘的刻度?
然後將整個形狀變成圓環,可以用 MASK 來實現,實現如下
clock{
/**/
border-radius: 50%;
-webkit-mask: radial-gradient(transparent 145px, red 0);
}
效果如下
其實,這裡還有一個小細節,黑色部分並不是居中的,需要修正一下(可以更改起始角度,指定 from)。然後,將這個草綠色換成透明就可以了,完整程式碼如下
clock{
/**/
background: repeating-conic-gradient(from -.5deg, #333 0 1deg, transparent 0deg 30deg);
border-radius: 50%;
-webkit-mask: radial-gradient(transparent 145px, red 0);
}
最終效果
分鐘的刻度也是同樣的道理,因為共有 60 個刻度,所以最小角度是 6 度(360 / 60),實現如下
clock{
/**/
background: repeating-conic-gradient(#333 0 1deg, #cddc39 0deg 6deg);
}
利用 CSS 背景可以無限疊加的特性,可以將這兩個背景繪製在同一個元素下,所以完整程式碼如下
clock{
/**/
background: repeating-conic-gradient(from -.5deg, #333 0 1deg, transparent 0deg 30deg),
repeating-conic-gradient(from -.5deg, #ccc 0 1deg, transparent 0deg 6deg);
border-radius: 50%;
-webkit-mask: radial-gradient(transparent 145px, red 0);
}
最終錶盤刻度效果如下
二、環形分佈的數字
看到這種佈局,我的第一反應其實是 textPath,這個 SVG 元素可以讓文字沿著指定路徑進行排列,比如下面這個 MDN 上的例子
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path id="MyPath" fill="none" stroke="red"
d="M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50" />
<text>
<textPath href="#MyPath">
Quick brown fox jumps over the lazy dog.
</textPath>
</text>
</svg>
效果如下
但是,這種方式有一個缺陷,無法改變文字的角度,只能沿著路徑垂直方向,而時鐘的數字方向都是正常的。
經過一番琢磨,發現還有一種方式也有類似沿著路徑的佈局方式,那就是 offset-path! 下面是 MDN 上的一個演示效果
那麼和環形排列數字有什麼關係呢?假設有這樣一個佈局
<clock-pane>
<num>1</num>
</clock-pane>
然後將這個數字指定到一個圓形的路徑上(目前僅支援 path )
num{
offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
}
效果如下(起始點是跟隨 path 路徑的)
然後,可以通過 offset-distance來改變元素在路徑上的位置,並且支援百分比
num{
offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
offset-distance: 100%
}
下面是從 0 到 100% 的變化
預設情況下元素的角度也是自適應垂直於路徑的,和 textPath 比較類似。但是我們可以手動指定固定角度,需要 offset-rotate,指定為 0deg 就行了
num{
offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
offset-rotate: 0deg;
offset-distance: 100%
}
效果如下,角度已經完全歸正了
如果有類似的佈局需求,是不是可以參考這個案例呢?
接下來,我們通過 CSS 變數,把 12 個數字自動歸位到指定位置
<clock-pane>
<num style="--i:1">1</num>
<num style="--i:2">2</num>
<num style="--i:3">3</num>
<num style="--i:4">4</num>
<num style="--i:5">5</num>
<num style="--i:6">6</num>
<num style="--i:7">7</num>
<num style="--i:8">8</num>
<num style="--i:9">9</num>
<num style="--i:10">10</num>
<num style="--i:11">11</num>
<num style="--i:12">12</num>
</clock-pane>
配合 calc 計算,完整程式碼如下
num{
position: absolute;
offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
offset-distance: calc( var(--i) * 10% / 1.2 - 25%);
offset-rotate: 0deg;
}
效果如下
三、自動執行的指標
三個指標的繪製應該沒有太大的難度,假設結構如下
<hour></hour>
<min></min>
<sec></sec>
需要注意一下旋轉的中心
hour{
position: absolute;
width: 4px;
height: 60px;
background: #333;
transform-origin: center bottom;
transform: translateY(-50%) rotate(30deg);
}
min{
position: absolute;
width: 4px;
height: 90px;
background: #333;
transform-origin: center bottom;
transform: translateY(-50%) rotate(60deg);
}
sec{
position: absolute;
width: 2px;
height: 120px;
background: red;
transform-origin: center bottom;
transform: translateY(-50%) rotate(90deg);
}
sec::after{
content: '';
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
left: 50%;
bottom: 0;
background: #fff;
border: 4px solid #333;
transform: translate(-50%, 50%);
}
效果如下
到這一步靜態佈局就算完成了,那麼如何執行呢?
之前在這篇文章 [還在使用定時器嗎?CSS 也能實現電子時鐘]() 有詳細講解到,不借助定時器,也完全可以用 CSS 動畫來實現!
回到這裡,執行的原理很簡單,就是一個無限迴圈的 CSS 動畫,如下
@keyframes clock {
to {
transform: translateY(-50%) rotate(360deg);
}
}
不同的是,時針、分針、秒針的週期不一樣,時針轉一圈是 12 小時、分針是 60 分鐘、秒針是 60 秒,各自需要換算成 秒數(CSS 單位只支援 秒 和 毫秒),為了方便測試,這裡將速度調快了 60s → 6s
程式碼實現就是(--step
是一分鐘)
hour{
/**/
transform: translateY(-50%) rotate(0);
animation: clock calc(var(--step) * 60 * 12) infinite;
}
min{
/**/
transform: translateY(-50%) rotate(0);
animation: clock calc(var(--step) * 60) infinite;
}
sec{
/**/
transform: translateY(-50%) rotate(0);
animation: clock var(--step) infinite;
}
效果如下
是不是有些奇怪?秒針在旋轉時先慢慢變快,然後又慢慢變慢,這是由於預設的動畫函式是ease
,所以需要改成linear
sec{
/**/
animation: clock var(--step) infinite linear;
}
這樣就好多了。不過平時所見的時鐘,秒針通常都那種走一下,停下的,還有一種“滴答滴答”的節奏感,並不是這種無縫的。在 CSS 動畫中,是不是有點像階梯狀,沒錯,可以用到 CSS 的 steps 函式,不瞭解這個的可以參考張老師的這篇文章:CSS3 animation屬性中的steps功能符深入介紹,實現如下
sec{
/**/
animation: clock var(--step) infinite steps(60);
}
效果如下
接下來需要通過 JS 初始化時間,僅此而已。需要注意的是,在獲取的時候加上偏移量,比如 12:30,分針其實是 12.5,以此類推。程式碼實現如下
const d = new Date()
const h = d.getHours();
const m = d.getMinutes();
const s = d.getSeconds();
clock.style.setProperty('--ds', s)
clock.style.setProperty('--dm', m + s/60)
clock.style.setProperty('--dh', h + m/60 + s/3600)
然後 CSS 中可以通過 animation-delay
來指定動畫的起始位置
hour{
/**/
animation: clock calc(var(--step) * 60 * 12) infinite linear;
animation-delay: calc( -1 * var(--step) * var(--dh) * 60);
}
min{
/**/
animation: clock calc(var(--step) * 60) infinite linear;
animation-delay: calc( -1 * var(--step) * var(--dm));
}
sec{
/**/
animation: clock var(--step) infinite steps(60);
animation-delay: calc( -1 * var(--step) * var(--ds) / 60 );
}
然後加點輪廓裝飾,就實現了文章開頭的效果
完整程式碼可以檢視 CSS clock
四、簡單總結一下
以上就是CSS 繪製時鐘的全部過程了,本文更側重於繪製過程,特別是是環形佈局技巧,這裡簡單總結一下
- 碰到環形圖案可以想到 conic-gradient
- 環形刻度繪製的原理是 conic-gradient 和 MASK 裁剪
- 文字沿路徑排列可以使用 textPath
- textPath 不支援改變文字角度
- offset-path 可以讓元素沿指定路徑進行佈局
- offset-path 可以設定元素在路徑的偏移和角度
- 時鐘自動執行的原理的是 CSS 動畫
- 時鐘、分鐘、秒鐘的區別是動畫時長不同
- “滴答滴答”的效果可以用 steps 實現
- 時間初始化可以通過 animation delay 實現
當然本文重點並不僅僅是在於實現了一個時鐘,而是在於 CSS 繪製技巧的積累和掌握。有了相關的積累,以後在碰到類似的佈局,在腦子裡過濾一下,馬上就能找到解決方案。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤
歡迎關注微信公眾號:前端偵探