基础圆环实现
css 实现圆环最简单的方法就是,将里面的圆盖在外面的圆上,让里面的圆颜色和外圆一样,看起来就像一个圆环了
优化圆环:使用 mask 进行切割
上面的做法是有一定的局限性的,对于单色背景还行,渐变色背景呢?甚至背景本身有动画效果呢?
最理想的方式是“真的”没有中间那一块
实现的方式是使用 mask 进行遮罩。这个遮罩类似于 ps 里的蒙版,把 svg/png 图片放在元素上,重合的地方保留,其他地方就直接切掉。
根据 svg/png 图片特有的属性,“透明”的部分就代表“没有”,一般黑色的部分就代表“有”
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100" height="100">
<circle cx="50" cy="50" r="47" fill="none" stroke="#000000" stroke-width="6" />
</svg>
这是一个典型的圆环,把这个圆环放在一个 div 上就能将 div 切出圆环的形状
.ring {
height: 200px;
width: 200px;
background-color: red;
position: absolute;
-webkit-mask-image: url(./assets/images/mysvg.svg);
mask-image: url(./assets/images/mysvg.svg);
-webkit-mask-size: 100%;
mask-size: 100%;
top: 50%;
left: 50%;
transform-origin: 0% 0%;
}
其中,生效的是(-webkit-)mask-image、(-webkit-)mask-size。分别指示了 svg 文件的路径和把 svg 遮盖在上面时 svg 图片的大小。注意 url 里内容不带引号。如果不幸 url 写错了,这个蒙版仍然会生效,但是会生效的有点厉害,整个都给遮盖了,让你以为是 svg 本身有问题改半天
css 的 mask 属性受支持但不完全受支持,在 chrome 里开发的时候就能发现,类似于 mask 开头的都被划掉了,但-webkit-mask 开头的就可以得到应用
个人猜测,可能有些平台适用加 webkit 的(比如说 chrome),有些不能加 webkit,所以每次写的时候两个都写上,如果只写一个会报 warning
关于 mask 及其相关属性:https://developer.mozilla.org/zh-CN/docs/Web/CSS/mask
mark-size 可以由被蒙的 div 的长宽的 x% 定义,也可以取 cover 和 contain 两个值,也可以同时用
指针运动实现
如何把指针“抠图”抠出来就不必赘述,依然使用 mask,指针运动就是小方块的 animation,但是 animation 的具体流程是需要讨论的
我想到的有两种方法,第一种方法是使用 transform: skew();
,通过角度的伸缩来进行
但是这种方式被证明是不可行的。skew 进行倾斜的时候并非像我们想象中的,从左下角开始倾斜,而是从中间点开始倾斜
第二种方法是使用transform: rotate();
这也更贴近真正钟表的旋转
有一些问题需要考虑:
- 各个方块做完需要 animation 后,何去何从?
- 最后 1/4 的时间段,方块不应保持原有的 90° 角,而是越来越小,如何实现?
第一个问题比较简单,animation 做完后,立即消失就好了
@keyframes rot3 {
0% {
transform: rotate(180deg);
}
25% {
transform: rotate(90deg);
opacity: 1;
}
25.1% {
transform: rotate(90deg);
opacity: 0;
}
100% {
transform: rotate(90deg);
opacity: 0;
}
}
0% 到 25% 是 div 旋转,25% 到 25.1% 就是立即消失
第二个问题,最容易想到的是遮盖,用一个块把左半边挡住
注意各个块的上下关系,1、2 在最上层,挡板在中间,3、4 在最下层
这些用 z-index 调即可
加入光晕
光晕和阴影事实上是一种东西,看颜色黑还是白咯
在这里选择filter: drop-shadow();
而非box-shadow
。drop-shadow
是真的牛,一张图说明一切(上面是 box-shadow,西下面是 drop-shadow)
#container {
filter: drop-shadow(0 0 40px hsl(270, 73%, 53%));
}
顺带一提,hsl 跟 rgb 一样,是一种色彩表示方式。
HSL:hue(色彩),saturation(饱和度),lightness(明度)
完整代码:
<div id="container">
<div id="round">
<div id="plate"></div>
<div class="occlude_fan"></div>
<div class="occlude_fan"></div>
<div class="occlude_fan"></div>
<div class="occlude_fan"></div>
<div id="occlude_rect"></div>
</div>
</div>
#container {
width: 500px;
height: 500px;
position: absolute;
filter: drop-shadow(0 0 40px hsl(270, 73%, 53%));
}
#round {
height: 400px;
width: 400px;
position: absolute;
top: 50px;
left: 30px;
border-radius: 50% 50% 50% 50%;
-webkit-mask-image: url(./assets/images/mask1.svg);
mask-image: url(./assets/images/mask1.svg);
-webkit-mask-size: cover;
mask-size: cover;
overflow: clip;
}
#plate {
height: 100%;
width: 100%;
position: absolute;
background-color: grey;
}
#occlude_rect {
height: 400px;
width: 200px;
background-color: grey;
position: absolute;
-webkit-mask-image: url(./mask1.svg);
mask-image: url(./mask1.svg);
-webkit-mask-size: cover;
mask-size: cover;
top: 0%;
left: 0%;
z-index: 3;
}
.occlude_fan {
height: 200px;
width: 200px;
background-color: hsl(270, 73%, 53%);
position: absolute;
-webkit-mask-image: url(./mask1-4.svg);
mask-image: url(./mask1-4.svg);
-webkit-mask-size: 100%;
mask-size: 100%;
top: 50%;
left: 50%;
transform-origin: 0% 0%;
filter: drop-shadow(0 0 40px hsl(270, 73%, 53%));
}
.occlude_fan:nth-of-type(2) {
transform: rotate(90deg);
animation: rot2 20s linear infinite;
z-index: 4;
}
.occlude_fan:nth-of-type(3) {
transform: rotate(180deg);
animation: rot3 20s linear infinite;
z-index: 4;
}
.occlude_fan:nth-of-type(4) {
transform: rotate(-90deg);
animation: rot4 20s linear infinite;
z-index: 2;
}
.occlude_fan:nth-of-type(5) {
transform: rotate(0deg);
animation: rot5 20s linear infinite;
z-index: 2;
}
@keyframes rot2 {
0% {
transform: rotate(90deg);
}
25% {
transform: rotate(90deg);
opacity: 1;
}
50% {
transform: rotate(0deg);
opacity: 1;
}
50.1% {
transform: rotate(0deg);
opacity: 0;
}
100% {
transform: rotate(0deg);
opacity: 0;
}
}
@keyframes rot3 {
0% {
transform: rotate(180deg);
}
25% {
transform: rotate(90deg);
opacity: 1;
}
25.1% {
transform: rotate(90deg);
opacity: 0;
}
100% {
transform: rotate(90deg);
opacity: 0;
}
}
@keyframes rot4 {
0% {
transform: rotate(-90deg);
}
75% {
transform: rotate(-90deg);
}
100% {
transform: rotate(-180deg);
}
}
@keyframes rot5 {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(0deg);
opacity: 1;
}
75% {
transform: rotate(-90deg);
opacity: 1;
}
75.1% {
transform: rotate(-90deg);
opacity: 0;
}
100% {
transform: rotate(-90deg);
opacity: 0;
}
}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="50" height="50">
<circle cx="0" cy="0" r="47" fill="none" stroke="#000000" stroke-width="7" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100" height="100">
<circle cx="50" cy="50" r="47" fill="none" stroke="#000000" stroke-width="6" />
</svg>
至此,效果如下:
好像还可以,但是有一个不能忍的问题:灰色条部分不应该发光却发光了。
这是因为左半边的灰色条,事实上是“挡板”裁出来的,是一个实体,所以自然也被镀上光了
我尝试过用一个 div 把 4 个块包起来,在该 container 上加 drop-shadow,结果给浏览器整不会了,摆烂,直接 shadow 一个都不显示——这也是很正常的,真实世界投影的时候,是不可能存在不透明的东西没有影子,放在它下面的东西却有影子的事情的。
这个问题不能解决,看起来我们上面所有的努力都白费了
新的思路——SVG
考虑在倒计时过程中的一帧,就是圆环一部分显示,一部分被切掉嘛
stroke-dasharray 这个属性刚好可以描绘这个,它的本意是将一个线变成变成虚线
下图就是 stroke-dasharray="5, 10, 20"的情况,5 实,10 空,20 实,5 空,10 实,20 空,形成了这样一个循环
当“实”和“空”的大小都足够大,就可以实现一条线里只有一循环
图片如何做出动画呢?仍然需要和 css 结合
所有 svg 显示属性都可以作为 css 属性来用
所以 css 动画放心使用 stroke-dasharray 就好了
@keyframes rot {
0% {
stroke-dasharray: 0 570;
}
100% {
stroke-dasharray: 570 0;
}
}
假设整个圆的周长是 570,动画开始时实线部分长 0,空的部分占 570,动画结束时实线部分长 570,空的部分是 0,视觉效果就是圆
对了,svg 可以直接插入 HTML 的,不一定要单独一个文件
<div id="clock-container">
<svg
width="240px"
height="240px"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<!-- 不动的灰色圆 -->
<circle
cx="110"
cy="110"
r="90"
stroke-width="10"
stroke="gray"
fill="none"
></circle>
<!-- 动的紫色圆 -->
<circle
cx="110"
cy="110"
r="90"
stroke-width="10"
stroke="hsl(270, 73%, 53%)"
fill="none"
class=" circle-load-svg"
></circle>
</svg>
</div>
能转了
改变方向
虽然能转起来了,但是起始点是歪的,在右边
transform: rotate()
对于 svg 也是适用的,不过不用加 deg 单位
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="hsl(270, 73%, 53%)" fill="none"
transform="rotate(90)" class=" circle-load-svg">
</circle>
新的光晕
现在紫色圆环不再是多个东西拼凑的结果,而是一个可以选择的对象
所以可以顺利地使用 css 的filter:blur()
但是好像这样性能会有问题:每次变化的时候网页都会重绘
另一种方式是使用 svg 的阴影
svg 阴影的底层实现是高斯模糊,使用时首先 define 一个 blur
<defs>
<filter id="f1">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" />
</filter>
</defs>
这里 feGaussianBlur 就是高斯模糊的意思
in 是输入值,SourceGraphic 就是说对元素自己进行模糊,还有一些其他的取值,比如说 SourceAlpha 表示只取元素透明度
stdDeviation 是用来控制模糊程度的,越大越模糊
一般来说模糊的外延最多不超过 10%,要是想要更大的模糊范围参考上面的链接吧
之后像 css id 一样引用就好了
<circle ... filter="url(#f1)">
</circle>
效果还可以,但是变透明了。其实用 css filter:blur 也会变透明
那就再加一层真环好了
至此代码如下
<div id="ontainer">
<svg width="1000px" height="1040px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="f1">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" />
</filter>
</defs>
<g transform="translate(0,300)rotate(-90)">
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="gray" fill="none"></circle>
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="hsl(270, 73%, 53%)" fill="none"
class=" circle-shadow" filter="url(#f1)">
</circle>
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="hsl(270, 73%, 53%)" fill="none"
class=" circle-load-svg">
</circle>
</g>
</svg>s
</div>
.circle-shadow .circle-load-svg {
animation: rot 5s linear infinite;
}
.circle-load-svg {
}
@keyframes rot {
0% {
stroke-dasharray: 570 0;
}
100% {
stroke-dasharray: 0 570;
}
}
样式优化
大体效果出来了,但是还是略丑了一些——线的边缘怎么这么方呢
svg 有一个stroke-linecap: round
属性可以轻松解决
除此之外,stroke-linejoin: round
也是很常用的,这个是指明线条拐角处的形状,用圆形