前端工程师进阶要点二——更多的使用CSS|小册免费学

402 阅读20分钟

CSS的强大——让CSS做更多的事

CSS的职责是负责定义元素如何展现,页面上所有元素的样式不管是依赖业务需求还是静态的,都要尽可能的交由CSS来完成。

树形结构与三角标

如下,是一个实现树形展开的列表:

代码的HTML如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>树UI</title>
</head>
<body>
  <ul class="tree">
    <li>项目1</li>
    <li>项目2</li>
    <li class="expand">项目3
      <ul>
        <li>子项3.1</li>
        <li>子项3.2</li>
        <li>子项3.3</li>
      </ul>
    </li>
    <li>项目4
      <ul>
        <li>子项4.1</li>
        <li>子项4.2</li>
      </ul>
    </li>
    <li>项目5</li>
  </ul>
</body>
</html>

但是默认的列表元素是小圆点:

使用图片实现树形的三角标

通过在list-style样式中指定图片,可以很简单的将圆点改为小三角。

如下,默认列表元素list-style为右三角图标;li元素的class添加expend后,list-style为下三角图标。从而实现想要的效果。

html, body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
}
ul {
  list-style: url("https://p5.ssl.qhimg.com/t018438001db494c5f3.png");
}
li.expand {
  list-style: url("https://p4.ssl.qhimg.com/t0167b045701562f010.png")
}
.tree li > ul {
  display: none;
}
.tree li.expand > ul {
  display: block;
}

这种实现方式不灵活且很消耗性能:

  1. list-style属性不能设置图片大小,所以需要提前根据文字大小设置图标大小;如果文字大小改变,也要调整图标的大小。
  2. 因为使用图片,因此会多出一些http请求。HTTP请求会导致网页的性能变差。

用CSS实现三角标

CSS的border属性

如下,一个div元素

<div id="block"></div>

设置宽高和边框:

#block {
  width: 100px;
  height: 50px;
  border-top: solid 10px blue;
  border-right: solid 10px;
  background-color: #ccc;
}

注意相邻两边的效果:

border-topborder-right交界的地方是一个斜角。

四个边各自颜色不同:

此时,如果再将div的宽高设置为0,则效果?

#block {
  width: 0px;
  height: 0px;
  border-top: solid 10px blue;
  border-right: solid 10px green;
  border-bottom: solid 10px red;
  border-left: solid 10px;
  background-color: #ccc;
}

/* #block.zore-w-h {
  transition: all 1s;
  width: 0px;
  height: 0px;
} */

如上,四个边框缩成了4个小三角形。然后,将上、右、下的边框都隐藏起来,就是小三角形的效果。

css的border实现三角形

通过改变相邻的border的宽度,可以得到不同角度的三角形。如果border-top/bottom:border-left/right等于1:根号3,则会得到正三角形。

修改

  • 元素的CSS样式如下:

    .tree li::before {
      color: #999;
      content: '';
      display: inline-block;
      width: 0;
      height: 0;
      border-style: solid;
      border-width: 6px 10.4px;
      border-color: transparent;
      border-left-color: currentColor;
      transform: translateX(6px);
    }
    

    借助li元素的::before伪元素绘制小三角:

    • border-width: 6px 10.4px将伪元素的上下边框设置为6px,左右边框设置为10.4px

    • border-color: transparent将伪元素的4个边框颜色设置为透明

    • border-left-color: currentColor将伪元素的左边框的颜色设置为当前元素(即border-left-color所属的该伪元素)的文字颜色

    • transform: translateX(6px)将伪元素向右偏移6px, 使小三角和列表项文字中间的间隔不至于太宽。

    列表的子项展开时,要将右三角形变为下三角形。通过transform属性对::before伪元素进行旋转实现:

    .tree li.expand::before {
      transform: rotate(90deg) translateX(6px) ;
    }
    

    使用CSS完成图形UI效果

    使用CSS绘制UI是一个很好实现思路,进而可以实现比较复杂的一些图形:

    纯css可以实现很多效果,如下图所示,都是使用css完成的几种图形。

    用CSS border属性绘制静态饼图

    如下,如何用border属性,纯css实现如下效果的饼图呢?

    通常HTML结构保持越简单,项目的JS代码也会相应简洁,代码的可维护性就会大大增强。因此,要尽量不增加冗余标签,保持HTML结构简单。

    简单饼图

    首先,实现一个单色饼图:

    <div class="pie"></div>
    
    .pie {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      background: #3c7;
    }
    

    借助border实现扇形

    /* border指定不同颜色 */
    .pie {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      background: #3c7;
      border: solid 10px;
      border-color: red blue orange green; /* 四个边不同颜色 */
      box-sizing: border-box;
    }
    
    /* width、height为0,border为75px */
    .pie {
      display: inline-block;
      width: 0;
      height: 0;
      border-radius: 50%;
      border: solid 75px;
      border-color: red blue orange green;
      box-sizing: border-box;
    }
    
    /* 上边框和右边框为绿色(#3c7),下边框和左边框为蓝色(#37c) */
    .pie {
      display: inline-block;
      width: 0;
      border-radius: 50%;
      border: solid 75px;
      border-color: #3c7 #3c7 #37c #37c;
      box-sizing: border-box;
    }
    
    /* transform旋转45度 */
    .pie {
      display: inline-block;
      width: 0;
      border-radius: 50%;
      border: solid 75px;
      border-color: #3c7 #3c7 #37c #37c ;
      box-sizing: border-box;
      transform: rotate(45deg);
    }
    

    四个图的变化过程如下:

    按比例显示颜色

    如何让两种颜色,按照对应比例显示呢?

    基本思路如下:

    1. 在这个元素上叠加一层饼图;
    2. 初始状态下,我们将这张饼图的右半边(即,上、右边框)的颜色设置为蓝色;左半边(即,下、左边框)的颜色设置为透明色。 这样初始情况下,.pie元素右半边绿色的部分,被这一叠加层覆盖为蓝色,从视觉上看.pie元素此时的进度是0;
    3. 根据需求,以不同角度旋转这个叠加层,这样就实现了不同百分比的饼图。

    通过伪元素实现叠加层

    .pie, .pie::before {
       display: inline-block;
       width: 0;
       border-radius: 50%;
       border: solid 75px;
    }
    
    .pie {
       box-sizing: border-box;
       position: relative;
       border-color: #3c7 #3c7 #37c #37c;
       transform: rotate(45deg);
    }
    .pie::before {
       content: '';
       position: absolute;
       border-color: #37c #37c transparent transparent;
       transform: translate(-50%, -50%); /*移动到和元素重叠*/
    }
    

    现在,伪元素重叠覆盖了原本的元素。旋转伪元素一个角度,比如10%,rotate(0.1turn)

    .pie::before {
       content: '';
       position: absolute;
       border-color: #37c #37c transparent transparent;
       transform: translate(-50%, -50%) rotate(0.1turn); /*移动到和元素重叠*/
    }
    

    如下,通过动画演示before伪元素(修改下一半颜色)旋转显示:

    .pie::before {
       content: '';
       position: absolute;
    
       transform: translate(-50%, -50%) ; /*移动到和元素重叠*/
       animation:pie-rotate 5s linear infinite;
       
       opacity: .6;
       border-color: #999 #999 transparent transparent;
    }
    
    @keyframes pie-rotate { 
       from { transform:translate(-50%, -50%); } 
       to { transform:translate(-50%, -50%) rotate(0.5turn); }
    }
    

    rotate-half-poe.gif

    关于多颜色饼图中心的小白点问题

    以上所有的多中颜色的border中,都可以看出来中心有个小白点!!!

    这个问题是因为border宽度设置的问题,即border: solid 75px;,将其改为偶数即可解决。

    .pie {
       display: inline-block;
       width: 0;
       border-radius: 50%;
       border: solid 76px;
       border-color: #3c7 #3c7 #37c #37c ;
       box-sizing: border-box;
       transform: rotate(45deg);
    }
    
    <div class="pie"></div>
    

    完整实现按比例显示饼图

    目前的饼图,只能实现到50%的进度显示,即前半圈显示没问题,但是后半圈显示则不行,比如60%:

    .pie::before {
      content: '';
      position: absolute;
      border: solid 76px;
      border-color: #37c #37c transparent transparent;
      transform: translate(-50%, -50%) rotate(0.6turn);
    }
    

    这是因为.pie元素只有右边一半是绿色,伪元素超过50%,左边的蓝色就出现,且伪元素未透明的另一半(蓝色)也出现在.pie元素右边的绿色区域。这就是上图的效果。

    要解决这个问题,可以找初始的状态:.pie元素左蓝右绿,通过添加一个左透明右蓝色的伪元素,组成完整的圆;伪元素旋转50%后,变为右透明(底部的.pie一半绿色全部显示出来)左蓝色(遮住.pie原本的蓝色),然后将伪元素设置为右绿色(遮住底部的绿色)左透明(显示.pie原本的蓝色),即:将伪元素原来是透明色的下、左边框设置为绿色,将原来是蓝色的上、右边框设置为透明色。

    <div class="pie convex"></div>
    
    .pie.convex::before {
      border-color: transparent transparent #3c7 #3c7;
    }
    

    如下:

    这样就使用CSS实现了静态的饼图效果

    通常统计数据都是来自服务器,需要动态绑定饼图的进度,而不是静态显示。

    一般,可以通过内联CSS的方式动态绑定进度数据;或通过js修改CSS属性值。但是伪元素无法使用内联的样式JS操作也不方便(需要替换操作整个伪元素内容)。

    伪元素无法像普通HTML元素一样,操作DOM对象及其样式。伪元素只能通过CSS伪类规则来操作,比较繁琐麻烦、

    CSS动画结合border实现动态绑定进度的饼图

    绘制动态绑定进度的饼图

    由于伪元素的属性无法通过内联的方式绑定,因此,要将与进度相关的数据从伪元素的属性转移到元素的属性中。

    可以结合伪元素的CSS动画及元素的animation-delay属性来控制实现:

    CSS动画实现饼图百分比显示的变化

    .pie::before {
      content: '';
      position: absolute;
      border: solid 76px;
      border-color: #37c #37c transparent transparent;
      transform: translate(-50%, -50%) rotate(.0turn);
      animation: spine 10s linear infinite,
        convex 10s step-end infinite;
    }
    
    @keyframes spine {
      to {transform: translate(-50%, -50%) rotate(1turn);}
    }
    
    @keyframes convex {
      50% {border-color: transparent transparent #3c7 #3c7;}
    }
    

    如上,.pie::before伪元素添加了一个动画声明:

    animation: spine 10s linear infinite,convex 10s step-end infinite;
    

    spine动画执行周期10秒,匀速执行,无限循环;convex动画执行10s,以step-end方式跳跃执行,无限循环。

    step-end是每一关键帧在动画进度的末尾改变属性状态。也就是到达帧动画@keyframes convex中的百分比进度(或from/to进度)时,属性才会改变。不同于linear匀速改变、ease变速改变,step-end到达进度末尾时直接改变。

    linear是每一关键帧在动画执行期间以播放时间均匀插值的方式改变属性状态。

    @keyframes spine {
      to {transform: translate(-50%, -50%) rotate(1turn);}
    }
    

    @keyframes spine声明spine帧动画,to是100%的缩写,表示动画完整周期结束时,元素的属性到达的状态。transform: translate(-50%, -50%) rotate(1turn);表示动画结束时.pie::before元素的状态为转过一圈,位置向左向上偏移50%。

    @keyframes convex {
      50% {border-color: transparent transparent #3c7 #3c7;}
    }
    

    @keyframes convex动画为:当动画执行到50%时,将.pie::before的状态修改为:border-color: transparent transparent #3c7 #3c7;。也就是当动画播放到50%时,伪元素的边框的颜色变为上、右边框为透明色,下、左边框为绿色。由于执行该动画是step-end方式跳跃执行,在50%时会直接改变颜色值,100%/0%时则改回来,正好对应旋转时的百分比需求

    饼图动画效果如下:

    dynamic-css-pie.gif

    动态绑定进度的饼图

    实现了饼图的变化,则通过animation-play-stateanimation-delay的结合,可以将饼图设置为固定的比例:

    .pie::before {
      content: '';
      position: absolute;
      border: solid 76px;
      border-color: #37c #37c transparent transparent;
      transform: translate(-50%, -50%) rotate(.0turn);
      animation: spine 10s linear infinite,
        convex 10s step-end infinite;
      animation-play-state: paused;
      animation-delay: -3s;
    }
    
    • animation-play-state: paused表示暂停动画
    • animation-delay: -3s表示将动画提前3秒执行,也就是说,动画初始的状态就在3s那一帧,即动画处于30%处,刚好对应进度为30%的饼图。

    这样,通过控制animation-delay属性,设置饼图的比例。

    但是animation-delay属性设置在伪元素上,而又不能通过伪元素直接修改这个属性。是否可以修改.pie元素的animation-delay来影响伪元素的状态呢?

    借助animation-delay属性的inherit值,继承父元素的animation-delay,从而实现。.pie::before是伪元素。

    伪元素会渲染为对应元素的子元素。

    即:将.pie::before伪元素的animation-delay设为inherit,然后将animation-delay值作为内联样式直接设置在.pie元素上,这样伪元素就能继承.pie元素上的animation-delay值。

    .pie::before {
      content: '';
      position: absolute;
      border: solid 76px;
      border-color: #37c #37c transparent transparent;
      transform: translate(-50%, -50%) rotate(.0turn);
      animation: spine 10s linear infinite,
        convex 10s step-end infinite;
      animation-play-state: paused;
      animation-delay: inherit;
    }
    

    实现显示不同进度百分比的饼图:

    <div class="pie" style="animation-delay: -1s;"></div>
    <div class="pie" style="animation-delay: -2.5s;"></div>
    <div class="pie" style="animation-delay: -5s;"></div>
    <div class="pie" style="animation-delay: -8s;"></div>
    

    这样就实现了,基本的动态绑定进度的饼图。

    饼图中的文字

    因为用了0宽高的元素和border来实现饼图,所以无法直接将文字直接写在.pie元素中。唯一的办法是给文字套一层HTML标签(如<span>),并脱离文档流

    <div class="pie" style="animation-delay: -1s;"><span>10%</span></div>
    <div class="pie" style="animation-delay: -2.5s;"><span>25%</span></div>
    <div class="pie" style="animation-delay: -5s;"><span>50%</span></div>
    <div class="pie" style="animation-delay: -8s;"><span>80%</span></div>
    
    .pie,
    .pie::before {
      display: inline-block;
      width: 0;
      border-radius: 50%;
      font-size: 0; /* 纯粹防止空白符,这个例子中可以不添加这个设置*/
    }
    
    .pie span {
      font-size: 1rem;
      position: absolute;
      color: #fff;
      transform: translate(-50%, -50%) rotate(-45deg);
    }
    

    效果如下:

    使用线性渐变实现饼图

    CSS的线性渐变实现双色饼图及原理

    关于饼图中文字的精简优化

    上面的实现,为了显示文字内容,通过增加<span>来实现。是的HTML不再是最简的结构。

    下面看看,如何不增加额外标签的情况下,实现文字的显示,即:使用CSS的线性渐变——linear-gradient()函数实现饼图的效果。

    首先,从纯色的圆形开始:

    <div class="pie"></div>
    

    background增加一个线性渐变

    .pie {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      background: linear-gradient(#37c,#3c7); 
    }
    

    background: linear-gradient(#37c,#3c7).pie元素从上到下颜色从绿色(#37c)均匀过渡到蓝色(#3c7)的背景。

    可以看到,绿色到蓝色的变化是均匀的在整个元素内渐变,但是可以通过linear-gradient设置渐变开始的位置。

    比如:把绿色开始渐变的位置设置在元素高度的50%的位置,而把蓝色开始渐变的位置也设置在50%的位置,那么这两个颜色刚好在50%的地方直接切换:

    .pie {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      background: linear-gradient(#37c 50%,#3c7 50%);
    }
    

    background: linear-gradient(#37c 50%,#3c7 50%)表示从起始位置开始到高度50%位置是蓝色,然后从50%开始到结束是绿色,而从50%到50%的地方则是蓝色到绿色的渐变范围。因为50%到50%并没有范围,所以渐变的部分就是一条分割线。

    这样一个双色的圆就出现了。

    然后默认可以使用transform对元素旋转90度。但是,还可以使用linear-gradient函数的渐变轴参数

    默认渐变轴是180度,渐变的方向是从上到下渐变轴为0度时,渐变方向是从下到上渐变轴的度数增加是按顺时针方向变化的。

    如下,将渐变轴角度改为90度,则渐变顺时针旋转90度,即从左到右渐变,实现左右的双色半圆形

    .pie {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      background: linear-gradient(90deg, #37c 50%,#3c7 50%);
    }
    

    显示文字

    由于没有使用border,元素有宽高,这样可以直接在元素上设置文字:

    <div class="pie">10%</div>
    
    .pie {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
      background: linear-gradient(90deg, #37c 50%,#3c7 50%);
      color: #fff;
      font-size: 1rem;
      line-height: 150px;
      text-align: center;
    }
    

    伪元素实现与进度对应的饼图

    和之前一样,我们通过伪元素拼接到原元素上,实现与进度数据对应的饼图

    .pie, .pie::before {
      display: inline-block;
      width: 150px;
      height: 150px;
      border-radius: 50%;
    }
    .pie {
      position: relative;
      background: linear-gradient(90deg, #37c 50%,#3c7 50%);
      color: #fff;
      font-size: 1rem;
      line-height: 150px;
      text-align: center;
    }
    
    .pie::before {
      position: absolute;
      left: 0;
      top: 0;
      content: '';
      background: linear-gradient(90deg, transparent 50%,#37c 50%);
    }
    

    伪元素使用linear-gradient设置背景色渐变,左边透明,右边是蓝色。

    效果如下:

    但是,会发现一个问题,文字被盖住了一部分(%),这是因为伪元素是.pie子元素,它的层级比文字层级高

    但是,如果伪元素的层级z-index较小,它就跑到.pie元素背景的后边:

    .pie::before {
      position: absolute;
      left: 0;
      top: 0;
      content: '';
      background: linear-gradient(90deg, transparent 50%,#37c 50%);
      z-index: -1; /*将伪元素的叠级变小*/
    }
    

    那么,最简单的解决办法是将.pie.pie::before的背景对调,让伪元素做底层饼图,.pie元素为叠加层:

    .pie, .pie::before {
        display: inline-block;
        width: 150px;
        height: 150px;
        border-radius: 50%;
    }
    .pie {
        position: relative;
        background: linear-gradient(90deg, transparent 50%,#37c 50%);
        color: #fff;
        font-size: 1rem;
        line-height: 150px;
        text-align: center;
    }
    
    .pie::before {
        position: absolute;
        left: 0;
        top: 0;
        content: '';
        background: linear-gradient(90deg, #37c 50%,#3c7 50%);            
        z-index: -1;
    }
    

    这样,就可以通过旋转上层饼图来实现与进度数据相对应的饼图效果。不过,不需要旋转.pie元素,而是调整linear-gradient的渐变轴来实现:

    .pie {
      color: #fff;
      background: linear-gradient(.35turn, transparent 50%,#37c 50%);
      font-size: 1rem;
      line-height: 150px;
      text-align: center;
    }
    

    如上,元素背景色的渐变轴调为.35turn

    rotate一样,渐变轴旋转可以用turn做单位,90deg相当于0.25turn,再旋转0.1turn也就是0.35turn,这时候刚好是10%的进度。

    同样,进度在超过50%时,要改变.pie元素的颜色分配:

    .pie.convex {
      background: linear-gradient(.85turn, #3c7 50%, transparent 50%);
    }
    
    <div class="pie convex">60%</div>
    

    结合js完整实现饼图进度效果

    线性渐变linear-gradient的饼图实现可以通过渐变轴的角度改变表现对应的比例,并且属性是在.pie元素上,而不是伪元素。因此可以通过js实现进度控制。

    <div class="pie" data-percentage="0.1">10%</div>
    <div class="pie" data-percentage="0.25">25%</div>
    <div class="pie" data-percentage="0.5">50%</div>
    <div class="pie" data-percentage="0.8">80%</div>
    
    .pie, .pie::before {
        display: inline-block;
        width: 150px;
        height: 150px;
        border-radius: 50%;
    }
    
    .pie {
        position: relative;
        color: #fff;
        font-size: 1rem;
        line-height: 150px;
        text-align: center;
    }
    
    .pie::before {
        position: absolute;
        left: 0;
        top: 0;
        content: '';
        background: linear-gradient(90deg, #37c 50%,#3c7 50%);
        z-index: -1;
    }
    
    function initPieGraph() {
        const graphs = document.querySelectorAll('.pie');
        graphs.forEach((graph) => {
            const percentage = parseFloat(graph.dataset.percentage);
            if (percentage <= 0.5) {
                graph.style.background = `linear-gradient(${percentage + .25}turn, transparent 50%,#37c 50%)`;
            } else {
                graph.style.background = `linear-gradient(${percentage + .25}turn, #3c7 50%, transparent 50%)`;
            }
        });
    }
    initPieGraph();
    

    比较遗憾的是,CSS动画没办法直接控制linear-gradient中的渐变轴的角度,让它线性变化

    linear-gradient线性渐变的角度方向

    默认渐变轴是180度,渐变的方向是从上到下渐变轴为0度时,渐变方向是从下到上渐变轴的度数增加是按顺时针方向变化的。

    如下,是不同角度线性渐变的方向:

    [class^=pie-]{
        display: inline-block;
        width: 150px;
        height: 150px;
        border-radius: 50%;
    }
    
    .pie-0{
        background: linear-gradient(0deg, #37c 50%,#3c7 50%);
    }
    .pie-90{
        background: linear-gradient(90deg, #37c 50%,#3c7 50%);
    }
    .pie-180{
        background: linear-gradient(180deg, #37c 50%,#3c7 50%);
    }
    .pie-d{
        background: linear-gradient(#37c 50%,#3c7 50%);
    }
    .pie-270{
        background: linear-gradient(270deg, #37c 50%,#3c7 50%);
    }
    
    <div class="pie-0">0度</div>
    <div class="pie-90">90度</div>
    <div class="pie-180">180度</div>
    <div class="pie-d">默认</div>
    <div class="pie-270">270度</div>
    

    各个角度的方向如下:

    CSS动画结合线性渐变实现动态饼图

    线性渐变的饼图实现了HTML结构的简洁,但是该版本中,使用了js,且js控制的是整个背景的linear-gradient函数,而不是单纯的渐变轴角度。最好的办法是通过单一值实现进度变化。

    border结合css动画的版本,就实现了单一控制animation-delay值实现动态进度。

    下面,看一下,如何固定渐变轴,实现transform旋转,纯css单一值控制拼图的进度。

    注意,旋转操作,尽量只旋转伪元素,因为伪元素是子类,如果旋转父类,子类也会跟着一起旋转,这在较复杂的操作时很容易混乱。

    因此,我们需要旋转.pie的伪元素,但是之前为了显示文字,伪元素的z-index为负值,位于.pie的下层。

    为了旋转下层的伪元素有效果,可以把上层.pie元素的左半圆从透明设置为绿色,右半圆初始为透明(显示出子元素的绿色)

    <div class="pie">0%</div>
    
    .pie, .pie::before {
        display: inline-block;
        width: 150px;
        height: 150px;
        border-radius: 50%;
    }
    
    .pie {
        position: relative;
        color: #fff;
        font-size: 1rem;
        line-height: 150px;
        text-align: center;
        background: linear-gradient(.25turn, #3c7 50%,transparent 50%);
    }
    
    .pie::before {
        position: absolute;
        left: 0;
        top: 0;
        content: '';
        background: linear-gradient(90deg, #37c 50%,#3c7 50%);
        z-index: -1;
    }
    

    初始饼图如下:

    旋转.pie::before伪元素:

    .pie::before {
      position: absolute;
      left: 0;
      top: 0;
      content: '';
      background: linear-gradient(90deg, #37c 50%,#3c7 50%);
      z-index: -1;
      transform: rotate(0turn);
      animation: spin 5s linear infinite;
      /* animation-play-state: paused;
      animation-delay: inherit; */
    }
    
    @keyframes spin{
      to {transform: rotate(1turn)}
    }
    

    同样,在超过50%是,会出现问题:

    如下,将.pie左半圆设置为透明,查看的效果

    rotate-linear-gradient-css-pie.gif

    在超过50%时,需要将.pie的背景颜色对调:

    .pie {
      position: relative;
      color: #fff;
      font-size: 1rem;
      line-height: 150px;
      text-align: center;
      background: linear-gradient(.25turn, #3c7 50%,transparent 50%);
      animation: convex 5s step-end infinite;
      /* animation-play-state: paused;
      animation-delay: -0s; */
    }
    @keyframes convex{
      50% {background: linear-gradient(90deg, transparent 50%, #37c 50% 0);}
    }
    

    step-end形式的动画,执行到50%的时候,.pie元素的背景色设置为左边透明色,右边为蓝色。

    效果变化如下:

    rotate-linear-gradient-pie.gif

    然后通过内联的animation-delay属性实现控制:

    <div class="pie" style="animation-delay: -.5s;">10%</div>
    <div class="pie" style="animation-delay: -1.25s;">25%</div>
    <div class="pie" style="animation-delay: -2.5s;">50%</div>
    <div class="pie" style="animation-delay: -4s;">80%</div>
    
    .pie {
        position: relative;
        color: #fff;
        font-size: 1rem;
        line-height: 150px;
        text-align: center;
        background: linear-gradient(.25turn, #3c7 50%,transparent 50%);
        animation: convex 5s step-end infinite;
        animation-play-state: paused;
        animation-delay: -0s;
    }
    @keyframes convex{
        50% {background: linear-gradient(90deg, transparent 50%, #37c 50% 0);}
    }
    
    .pie::before {
        position: absolute;
        left: 0;
        top: 0;
        content: '';
        background: linear-gradient(90deg, #37c 50%,#3c7 50%);
        z-index: -1;
        transform: rotate(0turn);
        animation: spin 5s linear infinite;
        animation-play-state: paused;
        animation-delay: inherit;
    }
    
    @keyframes spin{
        to {transform: rotate(1turn)}
    }
    

    借助Scss简化css代码

    目前为止,该方案还有不足。比如,代码中重复的值,如#37c#3c7颜色值;widthheightline-height等是可变值,且各自相同。

    如果后面需求变更,改变颜色或大小时,需要修改多处,如果漏掉一处,就会产生问题。

    所以,可以考虑,使用类似Scss的CSS预处理器。通过更加编程的方式,实现控制。

    比如,如下Scss:

    $fg: #3c7;
    $bg: #37c;
    $radius: 150px;
    
    .pie, .pie::before {
      display: inline-block;
      width: $radius;
      height: $radius;
      border-radius: 50%;
    }
    
    .pie {
      position: relative;
      color: #fff;
      font-size: 1rem;
      line-height: $radius;
      text-align: center;
      background: linear-gradient(.25turn, $fg 50%,transparent 50%);
      animation: convex 10s step-end infinite;
      animation-play-state: paused;
      animation-delay: -0s;
    }
    
    .pie::before {
      position: absolute;
      left: 0;
      top: 0;
      content: '';
      background: linear-gradient(90deg, $bg 50%,#$fg 50%);
      z-index: -1;
      transform: rotate(0turn);
      animation: spin 10s linear infinite;
      animation-play-state: paused;
      animation-delay: inherit;
    }
    
    @keyframes spin{
      to {transform: rotate(1turn)}
    }
    @keyframes convex{
      50% {background: linear-gradient(90deg, transparent 50%, $bg 50% 0);}
    }
    

    然后,再生成对应的CSS。便于以后修改参数。

    CSS自定义属性

    对于较新的浏览器,还可以使用CSS自定义属性,动态地定义层叠式变量:

    .pie, .pie::before {
      --radius: 150px;
      --fg-color: #3c7;
      --bg-color: #37c;
      display: inline-block;
      width: var(--radius);
      height: var(--radius);
      border-radius: 50%;
      position: relative;
    }
    
    .pie {
      color: #fff;
      font-size: 1rem;
      line-height: var(--radius);
      text-align: center;
      background: linear-gradient(.25turn, var(--fg-color) 50%,transparent 50%);
      animation: convex 10s step-end infinite;
      animation-play-state: paused;
      animation-delay: -0s;
    }
    
    .pie::before {
      position: absolute;
      left: 0;
      top: 0;
      content: '';
      background: linear-gradient(90deg, var(--bg-color) 50%,var(--fg-color) 50%);
      z-index: -1;
      transform: rotate(0turn);
      animation: spin 10s linear infinite;
      animation-play-state: paused;
      animation-delay: inherit;
    }
    
    @keyframes spin{
      to {transform: rotate(1turn)}
    }
    @keyframes convex{
      50% {background: linear-gradient(90deg, transparent 50%, var(--bg-color) 50% 0);}
    }
    

    如上,定义了三个自定义属性:

    {
      --radius: 150px;
      --fg-color: #3c7;
      --bg-color: #37c;
    }
    

    然后通过var()动态的引入这些值。

    当然,饼图的实现,除了CSS,还可以考虑SVG或Canvas等。

    本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情