用CSS实现圆环倒计时

基础圆环实现

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();这也更贴近真正钟表的旋转

请添加图片描述

有一些问题需要考虑:

  1. 各个方块做完需要 animation 后,何去何从?
  2. 最后 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-shadowdrop-shadow是真的牛,一张图说明一切(上面是 box-shadow,西下面是 drop-shadow)

请添加图片描述

具体区别见:
https://www.zhangxinxu.com/wordpress/2016/05/css3-filter-drop-shadow-vs-box-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

这种困境不是我一个人遇到,很多的前辈都遇到过:
https://www.cnblogs.com/coco1s/p/6225973.html

考虑在倒计时过程中的一帧,就是圆环一部分显示,一部分被切掉嘛

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>

新的光晕

参考:https://juejin.cn/post/6844903492646092807

现在紫色圆环不再是多个东西拼凑的结果,而是一个可以选择的对象

所以可以顺利地使用 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也是很常用的,这个是指明线条拐角处的形状,用圆形
具体例子见:https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute/stroke-linejoin

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现进度条,你可以使用 CSS 的伪元素和 transform 属性来实现。以下是一个简单的示例: HTML: ```html <div class="progress-bar"> <div class="progress"></div> </div> ``` CSS: ```css .progress-bar { position: relative; width: 200px; height: 200px; border-radius: 50%; background-color: lightgray; } .progress { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; background-color: #00bcd4; transform-origin: center center; } .progress::before { content: ""; position: absolute; top: -10px; left: calc(50% - 10px); width: 20px; height: 20px; border-radius: 50%; background-color: #00bcd4; } .progress::after { content: ""; position: absolute; top: calc(50% - 10px); right: -10px; width: 20px; height: 20px; border-radius: 50%; background-color: lightgray; } .progress-bar::after { content: ""; position: absolute; top: calc(50% - 30px); left: calc(50% - 30px); width: 60px; height: 60px; border-radius: 50%; background-color: lightgray; } .progress-bar::before { content: ""; position: absolute; top: calc(50% - 40px); left: calc(50% - 40px); width: 80px; height: 80px; border-radius: 50%; background-color: lightgray; } ``` 在上面的示例中,我们有一个包含两个 div 的容器。`.progress-bar` 是的外部容器,`.progress` 是实际的进度条。我们使用伪元素 `::before` 和 `::after` 来创建的开始和结束点。 通过改变 `.progress` 元素的 `transform` 属性,你可以调整进度条的进度。例如,你可以使用 `transform: rotate(45deg)` 来表示进度条完成了 45%。 你可以根据你的需求修改样式,并使用 JavaScript 来动态更新进度条的进度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值