• 用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>
    
    • 1
    • 2
    • 3

    这是一个典型的圆环,把这个圆环放在一个 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%;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其中,生效的是(-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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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%));
    }
    
    • 1
    • 2
    • 3

    顺带一提,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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    #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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    <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>
    
    • 1
    • 2
    • 3
    <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>
    
    • 1
    • 2
    • 3

    至此,效果如下:

    请添加图片描述

    好像还可以,但是有一个不能忍的问题:灰色条部分不应该发光却发光了。

    这是因为左半边的灰色条,事实上是“挡板”裁出来的,是一个实体,所以自然也被镀上光了

    我尝试过用一个 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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    假设整个圆的周长是 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    能转了

    请添加图片描述

    改变方向

    虽然能转起来了,但是起始点是歪的,在右边

    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>
    
    • 1
    • 2
    • 3

    新的光晕

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

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

    所以可以顺利地使用 css 的filter:blur()

    但是好像这样性能会有问题:每次变化的时候网页都会重绘

    另一种方式是使用 svg 的阴影

    svg 阴影的底层实现是高斯模糊,使用时首先 define 一个 blur

      <defs>
        <filter id="f1">
          <feGaussianBlur in="SourceGraphic" stdDeviation="10" />
        filter>
      defs>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里 feGaussianBlur 就是高斯模糊的意思
    in 是输入值,SourceGraphic 就是说对元素自己进行模糊,还有一些其他的取值,比如说 SourceAlpha 表示只取元素透明度
    stdDeviation 是用来控制模糊程度的,越大越模糊
    一般来说模糊的外延最多不超过 10%,要是想要更大的模糊范围参考上面的链接吧

    之后像 css id 一样引用就好了

    <circle ... filter="url(#f1)">
    circle>
    
    • 1
    • 2

    请添加图片描述

    效果还可以,但是变透明了。其实用 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    .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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    请添加图片描述

    样式优化

    大体效果出来了,但是还是略丑了一些——线的边缘怎么这么方呢

    svg 有一个stroke-linecap: round属性可以轻松解决

    请添加图片描述

    除此之外,stroke-linejoin: round也是很常用的,这个是指明线条拐角处的形状,用圆形
    具体例子见:https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute/stroke-linejoin

  • 相关阅读:
    负担过重何以轻装前行
    解决Docker容器apt无法下载问题
    面试官:你说你用过Dubbo,那你说说看Dubbo的SPI
    股指期货如何交易?有哪些交易策略和操作技巧?
    【附源码】计算机毕业设计java员工工资管理系统设计与实现
    【统计学概念】初学者指南:了解置信区间
    css过渡属性的简单使用
    React 之 forwardRef用法(十六)
    Vuex中Mutation 的基本使用
    【Shell脚本6】Shell 运算符
  • 原文地址:https://blog.csdn.net/weixin_43544179/article/details/126674435