最近在网上冲浪,发现CSS里有个叫conic-gradient(圆锥渐变)的玩意儿挺冷门,但玩起来是真滴炫酷。跟平时常见的线性渐变、径向渐变不太一样,圆锥渐变是绕着中心点转圈圈变色,就像切蛋糕一样,从某个角度开始一圈圈扫过去。很多项目里都见不着它的身影,属实有点孤单。今天就拿它整个活——做一个表盘,让指针带动渐变颜色实时跑起来,彻底把圆锥渐变的脾气摸透。
这个案例会用到HTML搭骨架,CSS画表盘和指针,再用JavaScript获取当前时间并计算指针旋转角度。核心是把圆锥渐变的起始角度和结束角度绑定到分针和时针上,实现渐变区域随指针动态变化。过程中会踩到角度大小比较的坑,得用负值来救场。整套代码量不大,但能玩出Apple Watch渐变表盘那味儿。
搭建骨架
先整点基础的HTML结构。需要一个容器来装整个表盘,里面放三根指针:秒针、分针、时针。代码长这样:
<div id="clock" class="clock">
<div class="hand second-hand" style="--second-hand-degrees: 0deg;"></div>
<div class="hand minute-hand" style="--minute-hand-degrees: 0deg;"></div>
<div class="hand hour-hand" style="--hour-hand-degrees: 0deg;"></div>
</div>这里用了行内样式里的CSS变量来存角度,后面JS会动态更新这些变量。注意每个div的类名要对应好,不然指针就乱套了。另外表盘容器得有个固定宽高,并且设成圆形,不然指针转起来歪歪扭扭的。
指针样式
写CSS让表盘和指针有个正经样子。关键点是用position: absolute把指针堆叠到中心,再用transform-origin: bottom center让指针底部固定在圆心,这样旋转时才不会飘走。
.clock {
--hour-hand-color: #000;
--hour-hand-degrees: 0deg;
--minute-hand-color: #000;
--minute-hand-degrees: 0deg;
--second-hand-color: hotpink;
--second-hand-degrees: 0deg;
position: relative;
width: 25vw;
min-width: 320px;
height: 25vw;
min-height: 320px;
border-radius: 50%;
margin: 0 auto;
border: 7px solid #000;
}
.hand {
position: absolute;
left: 50%;
bottom: 50%;
height: 45%;
width: 4px;
margin-left: -2px;
background: var(--second-hand-color);
border-radius: 6px;
transform-origin: bottom center;
transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1);
}
.second-hand {
transform: rotate(var(--second-hand-degrees));
}
.hour-hand {
height: 35%;
background-color: var(--hour-hand-color);
transform: rotate(var(--hour-hand-degrees));
}
.minute-hand {
height: 50%;
background: var(--minute-hand-color);
transform: rotate(var(--minute-hand-degrees));
}这里把指针颜色和角度都抽成了CSS变量,方便JS直接改。注意秒针默认用了骚粉色,分针和时针是黑色,想换色直接改变量就行。还有那个transition-timing-function用了奇怪的贝塞尔曲线,可以让指针转动时带点弹性效果,看着更带感。
时间计算
现在让指针真正动起来。写个JS函数获取当前时间,算出每根指针应该转多少度。一圈360度,秒针每秒走6度(360/60),分针每分钟也是6度,但为了更平滑,可以把秒针的角度也加进去让分针微微动。时针每小时走30度(360/12),同样可以加分钟的角度让时针慢慢挪。
const clock = document.getElementById("clock");
function setDate() {
const now = new Date();
const seconds = now.getSeconds();
const minutes = now.getMinutes();
const hours = now.getHours();
const secondsAngle = seconds * 6;
const minsAngle = (minutes * 6) + (secondsAngle / 60);
const hourAngle = ((hours % 12) / 12) * 360 + (minsAngle / 12);
clock.style.setProperty("--second-hand-degrees", secondsAngle + "deg");
clock.style.setProperty("--minute-hand-degrees", minsAngle + "deg");
clock.style.setProperty("--hour-hand-degrees", hourAngle + "deg");
}
setInterval(setDate, 1000);
setDate();这里有个小细节:hours % 12是为了把24小时制转成12小时制,不然下午两点会变成28点?不对,下午两点就是14,模12得2,正确。另外hourAngle里加了minsAngle / 12,因为60分钟对应时针走30度,所以每分钟时针走0.5度,把分钟角度除以12正好得到这个偏移量。跑一下代码,指针就能滴答滴答走了。
圆锥渐变映射
重头戏来了——把圆锥渐变糊到表盘上,并且让渐变范围跟着指针走。原理是用conic-gradient做一个从某个角度开始、到另一个角度结束的渐变条,这个渐变条会像手电筒一样扫过表盘。起始点定在分针位置,结束点定在时针位置。
先在CSS里给.clock加上圆锥渐变背景,并定义两个新变量--start和--end来控制起始和结束角度。
.clock {
/* 之前的变量不动 */
--start: 0deg;
--end: 0deg;
background: conic-gradient(
from var(--start),
rgb(255 255 255) 2deg,
rgb(0 0 0 / 0.5) var(--end),
rgb(255 255 255) 2deg,
rgb(0 0 0 / 0.7)
);
}这段渐变的意思是:从--start角度开始,先来2度的纯白色,然后渐变到半透明黑色直到--end角度,再来2度白色,最后用深色半透明填满剩下的圆。这样就会在分针和时针之间形成一个亮色扇形,其他区域偏暗。
然后在JS的setDate函数里,把分针角度作为起始点,时针角度减去起始点得到渐变长度。但这里容易翻车:当分针角度大于时针角度时,endPosition会变成负数,渐变就乱掉了。解决办法是检测到这种情况时,把起始点减掉360度变成负值,然后重新算结束点。
let startPosition = minsAngle;
let endPosition = hourAngle - minsAngle;
if (minsAngle > hourAngle) {
startPosition = minsAngle - 360;
endPosition = hourAngle - startPosition;
}
clock.style.setProperty("--start", startPosition + "deg");
clock.style.setProperty("--end", endPosition + "deg");举个例子:下午3点10分,时针大概在95度位置,分针在60度,这时候分针小于时针,没问题。但到了3点50分,时针走到约115度,分针跑到300度,分针大于时针了。如果不处理,endPosition = 115 - 300 = -185deg,圆锥渐变会乱转。处理之后startPosition = 300 - 360 = -60deg,endPosition = 115 - (-60) = 175deg,渐变就顺了。
负角救场
上面那个负角度方案是个经典骚操作。CSS的圆锥渐变完全支持负的from值,比如from -60deg,它会逆时针转60度作为起点。利用这个特性,当分针超过时针时,把起始点拉到负方向,让渐变区间依然保持正向流动。可以理解为:本来分针在后头追时针,追过头了就把表盘刻度盘倒着拧一点,让分针重新出现在时针前面。
实际跑起来效果非常丝滑,从0点到24点都不会出现渐变断裂或者反向跳变。另外注意秒针并没有参与渐变范围的映射,因为如果让渐变每秒都跳一下会太鬼畜,保持分钟级的平滑过渡就够了。
最后把完整JS代码整合一下:
const clock = document.getElementById("clock");
function setDate() {
const now = new Date();
const seconds = now.getSeconds();
const minutes = now.getMinutes();
const hours = now.getHours();
const secondsAngle = seconds * 6;
const minsAngle = (minutes * 6) + (secondsAngle / 60);
const hourAngle = ((hours % 12) / 12) * 360 + (minsAngle / 12);
clock.style.setProperty("--second-hand-degrees", secondsAngle + "deg");
clock.style.setProperty("--minute-hand-degrees", minsAngle + "deg");
clock.style.setProperty("--hour-hand-degrees", hourAngle + "deg");
let startPosition = minsAngle;
let endPosition = hourAngle - minsAngle;
if (minsAngle > hourAngle) {
startPosition = minsAngle - 360;
endPosition = hourAngle - startPosition;
}
clock.style.setProperty("--start", startPosition + "deg");
clock.style.setProperty("--end", endPosition + "deg");
}
setInterval(setDate, 1000);
setDate();这里把秒针也接上了,虽然渐变没用秒针数据,但指针本身会转,视觉上更完整。跑起来就能看到一个渐变扇形区域在表盘上慢慢游走,分针和时针之间的区域永远是最亮的。想换渐变颜色的话,改CSS里的rgb值就行,比如搞个赛博朋克紫绿渐变,绝对炸街。
