实现一个渐变空心圆

2,173 阅读3分钟

背景

最近在做移动端组件库开发,UI出一个loading的设计稿,渐变空心圆,实现过程比较曲折,记录一下。

image.png

备注:全文基于react实现,可以自行翻译成其他框架版本。

实现思路

渐变首先想到的是css3属性linear-gradient,如下代码

<div className="circle" />
.circle {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: linear-gradient(to bottom, #333, #fff);
}

效果:

image.png

好像跟UI稿差距有点大,中间需要挖空,最简单的办法,中间再放一个圆形div。

<div className="circle">
   <div className="inner-circle" />
</div>
.circle {
  width: 100px;
  height: 100px;
  padding: 10px;
  border-radius: 50%;
  background: linear-gradient(to bottom, #333, #fff);
}

.circle-inner {
  width: 100%;
  height: 100%;
  background-color: #fff;
  border-radius: 50%;
}

效果:

image.png

好像有点像了,但是这个渐变只能一个方向,跟UI稿的环形渐变还有差距,半个实现了,按照这个思路可以画2个半圆合在一起,这时候就需要3个圆。

<div className="circle">
    <div className="circle-left" />
    <div className="circle-right" />
    <div className="circle-inner" />
</div>

.circle {
  position: relative;
  width: 100px;
  height: 100px;
}

.circle-left {
  position: absolute;
  top: 0;
  left: 0;
  width: 50px;
  height: 100px;
  border-radius: 100px 0 0 100px;
  background: linear-gradient(to bottom, rgb(51, 51, 51), rgba(51, 51, 51, 0.5));
}

.circle-right {
  position: absolute;
  top: 0;
  right: 0;
  width: 50px;
  height: 100px;
  border-radius: 0 100px 100px 0;
  background: linear-gradient(to top, rgba(51, 51, 51, 0.5), rgba(51, 51, 51, 0));
}

.circle-inner {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 80px;
  height: 80px;
  border-radius: 40px;
  background-color: #fff;
}

效果:

image.png

这个时候已经很接近UI的效果了,但是底部的连接处不是很完美,明显左边颜色太深了,把连接处的透明度调淡一点效果会比较好,这是左边调成background: linear-gradient(to bottom, rgb(51, 51, 51), rgba(51, 51, 51, 0.2));,右边调成background: linear-gradient(to bottom, rgb(51, 51, 51), rgba(51, 51, 51, 0.25));

效果:

image.png

加上动画转起来效果会更好。

这样就实现了一个渐变空心圆,但是发来发现有一个致命的问题,linear-gradient不支持颜色+透明分开处理,导致无法轻松的修改颜色,通过查阅资料发现可以使用svg实现。

svg实现思路

跟纯css的实现思路相似,利用linearGradient画两个半圆,合并在一起,这里就直接贴代码了,感兴趣可以深入了解一下每一个api的作用,或者在评论区讨论。

<svg
width="240"
height="240"
viewBox="0 0 240 240"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
color="#666"
>
    <defs>
      <linearGradient id="linear-gradient1">
        <stop offset="0%" stop-color="currentColor" />
        <stop offset="50%" stop-color="currentColor" stop-opacity="30%" />
      </linearGradient>
      <linearGradient id="linear-gradient2">
        <stop offset="50%" stop-color="currentColor" stop-opacity="30%" />
        <stop offset="100%" stop-color="currentColor" stop-opacity="0" />
      </linearGradient>
      <circle
        id="semi-circle"
        cx="120"
        cy="120"
        r="100"
        stroke-width="10"
        stroke-dasharray="314 1000"
        fill="none"
      />
    </defs>
    <g>
      <use href="#semi-circle" stroke="url('#linear-gradient1')" />
      <use
        href="#semi-circle"
        stroke="url('#linear-gradient2')"
        style={{
          transform: "rotate(180deg)",
          transformOrigin: "center"
        }}
      />
    </g>
</svg>

效果:

image.png

关键是利用stop-color="currentColor",可以轻易修改渐变起始颜色。

遇到的坑

  1. svg渐变需要先定义linearGradient,然后通过id使用渐变色,在Chrome上多个svg,使用重复id,始终会渲染第一个赋值的颜色,在Safari上可以正确的显示颜色。

解决办法: 每一个svg的linearGradient id通过js动态生成

写在最后

本文介绍了2种实现渐变空心圆的思路,推荐大家使用svg实现,兼容性好,并且可以自定义颜色,放在组件里使用很方便。点此预览,F12可以直接查看代码