从画一个圆开始——CSS圆角探秘(二)

在上一篇《从画一个圆开始——CSS圆角探秘(一)》中,我们重新了解border-radius的一些属性。我们画好了正圆,了解了圆角,难道border-radius的功能仅限于此吗?

我想并不是,就如同在上一篇文章中border-radius完美结合box-shodow形成多重圆角边框一样。也许还有其他属性与它结合,会产生不一样的效果与功能。

三、一次发现

在有次逛别人博客时,我发现了这样一个进度条:161031_3

非常巧妙与友好的设计,我当时就想,这应该是用Canvas完成的吧。结果与我的想法一致,这里的确是用Canvas完成的。

我们知道,扇形在网页运用中极为普遍,尤其是在一些图表要求比较高的网页中,在之前我们实现饼图都极为复杂和繁琐,那有没有办法用CSS去实现呢?

答案是确定的,可以,在之后我查找了一些资料,的确找到了相应的方法。废话不多说,我们下面就来看看,用CSS如何实现扇形动画。

基于transform的解决方案:

这是一个非常简洁的解决方案,只需要一个元素作为容器,而其他 部分是由伪元素、变形属性和 CSS 渐变来实现的。

但是我在实现的过程中发现在伪元素实现会出现一些意外的BUG,所以在这里我并不会使用伪元素。那么我们将最开始的圆拿到这里:

HTML:

CSS:

.demo-3-1 {
    margin: 0 auto;
    width: 150px;
    height: 150px;
    border-radius: 50%;
    background: #5cb85c;
}

3.1 从最简单的扇形开始

下面我们从最简单的一个扇形开始,假设我们现在需要一个50%的扇形,该怎么实现呢?

当然,通过上一篇文章我们可以利用border-radius非常快的实现:HTML:

CSS:

.demo-3-1 {
    position: relative;
    margin: 0 auto;
    width: 150px;
    height: 150px;
    border-radius: 50%;
    background: #5cb85c;
}

.inner-3-1 {
    position: absolute;
    right: 0;
    top: 0;
    width: 75px;
    height: 150px;
    background: #333;
    border-radius: 0 100% 100% 0 / 0 50% 50% 0;
}

实例:

 

或者说用另外的一种方式,我们用线性渐变,拉出一个扇形。

CSS:

.demo-3-1 {
    position: relative;
    margin: 0 auto;
    width: 150px;
    height: 150px;
    background-color: #5cb85c;
    background-image: linear-gradient(to right, transparent 50%, #333 50%);
}
 

对比一下我们可以发现,同样是现实一个50%的扇形,第一种方式明显要比第二种更加繁琐一点,但如果我们这样去想呢:

将第二种方式作为底,inner-3-1这个元素作为这个遮罩,与底左边的颜色相同,那我们岂不是可以写出一个小于50%的扇形呢?如下图所示:

161031_5

通过观察图片,我们知道这个思路是完全可行的,接下来我们只需要将遮罩层覆盖到底层元素上并定位即可,但在这里我们需要思考:

  • 我们希望是遮盖住深色的部分,遮罩层应与底层的左边绿色保持一致,那么遮罩层只需要继承父元素的颜色即可。
  • 我们希望遮罩层围绕着圆心旋转,那么这个点就应该在遮罩层左边框的中心,因此我们就酱遮罩层 transform-origin 属性设置为 0 50%。
  • 如果我们未将遮罩层写成一个半圆,那么我们只需要为底层元素添加overflow: hidden 就可以隐藏掉遮罩层多余的部分。

通过上面的分析,我们就可以为遮罩层写上样式:

CSS:

.inner-3-1 {
    position: absolute;
    right: 0;
    top: 0;
    width: 75px;
    height: 150px;
    background: inherit;
    border-radius: 0 100% 100% 0 / 0 50% 50% 0;
    transform-origin: 0 50%;
}

实例:

 

那么做到这里,我们可以通过一个 rotate() 变形属性非常简单的让其旋转起来。 如果我们要显示出一个小于50%的比率,我们可以指定旋转的值小于 180deg,为了更方便,我们这里使用turn作为单位,1turn就是一圈的意思,这样做会更加直观一些。那么我们只需要设定一个小于.5turn的值就可以了。

CSS:

.inner-3-1 {
    position: absolute;
    right: 0;
    top: 0;
    width: 75px;
    height: 150px;
    background: inherit;
    border-radius: 0 100% 100% 0 / 0 50% 50% 0;
    transform-origin: 0 50%;
    transform: rotate(.1turn);
}

实例:

  这样,我们就可以轻轻松松完成一个小于50%的扇形。但在实际工作生产种,仅仅一个小于50%的扇形图起不了多大的作用,我们更希望能得到一个能0~100%显示的扇形图,那我们该如何实现呢?可能有小伙伴说,那我们把rotate()增加到.5turn以上,不过这样并不行。那么我们该如何用CSS去显示一个大于50%的饼图呢?在操作之前,我们来看看rotate()增加到.5turn以上的样子。

161031_4

如何去解决超过50%后显示的问题呢?在这里我们要求当值大于.5turn时改变遮罩层的颜色,由深色变为绿色。用JS判断?NO,这样显然不能。因为我们得用CSS来完成。那还有什么属性可以做到呢?

此时没有办法,那么我们先放下这个问题,想一想另外一个问题,扇形图不仅可以静止的显示数据,也可以动态的显示进度。那我们先让它动起来,这就非常简单了,我们只需要写一个CSS动画就可以轻松的实现:

CSS:

@keyframes start {
    to {
        transform: rotate(1turn);
    }
}

.inner-3-1 {
    position: absolute;
    right: 0;
    top: 0;
    width: 75px;
    height: 150px;
    background: inherit;
    border-radius: 0 100% 100% 0 / 0 50% 50% 0;
    transform-origin: 0 50%;
    animation: start 4s linear infinite;
}

实例:

 

在这里,我为遮罩层加上了红色的边框,让它更清晰的显示。我们很容易的解决了旋转的问题,但上面遗留的问题在这里依然是个痛点,这个时候让我们回想一下在上一篇文章扩展中提到的一句话:“box-shodow可以设定多组效果,每组参数值以逗号分隔。那么这样我们就可以轻松实现多重边框的效果,当然,每个边框不同的颜色我们也可以轻松实现。”

多组参数帮我们可以解决不同颜色的多重边框,那是否类似的道理,,多组动画也可以解决超过一半变色的问题呢?

Animation

默认值:看每个独立属性 适用于:所有元素,包含伪对象:after和:before 继承性:无 动画性:否 计算值:看每个独立属性 媒体:视觉 取值: : 检索或设置对象所应用的动画名称 : 检索或设置对象动画的持续时间 : 检索或设置对象动画的过渡类型 : 检索或设置对象动画延迟的时间 : 检索或设置对象动画的循环次数 : 检索或设置对象动画在循环中是否反向运动 : 检索或设置对象动画时间之外的状态 : 检索或设置对象动画的状态。w3c正考虑是否将该属性移除,因为动画的状态可以通过其它的方式实现,比如重设样式

Animation属于复合属性。检索或设置对象所应用的动画特效。

  • 如果提供多组属性值,以逗号进行分隔。
  • 如果只提供一个

那按照以往的思路,我们在不同需求的地方写上不同的样式不就可以了吗?所以我们就在超过50%的时候让遮罩层变色,这个非常好实现:

CSS:

@keyframes start-1 {
    to {
        transform: rotate(.5turn);
    }
}

@keyframes bg {
    50% {
        background: #333;
    }
}

.inner-3-1 {
    position: absolute;
    right: 0;
    top: 0;
    width: 75px;
    height: 150px;
    background: inherit;
    border-radius: 0 100% 100% 0 / 0 50% 50% 0;
    transform-origin: 0 50%;
    animation: start-1 4s linear infinite, bg 8s step-end infinite;
}

实例:

 

在这里要非常注意step-end,它是animation-timing-function(检索或设置对象动画的过渡类型)的一个值。在bg这个动画中,我们是设置的背景色变化,如果使用linear这样的值,它会呈现一个缓动的背景变化,所以我们需要干脆的step-end,它等同于steps(1, end),这样写第一个参数必须为正整数,它指定函数的步数。第二个参数取值可以是start或end,指定每一步的值发生变化的时间点。

嗯,好了动态的旋转起了,按照这个道理,那么我们静态的指定一个值也是OK的?那么如何让动画停在一个指定的位置上呢?

w3c标准在css-animations中写了这么一句话:

If the value for ‘animation-delay’ is a negative time offset then the animation will execute the moment it is applied, but will appear to have begun execution at the specified offset. That is, the animation will appear to begin part-way through its play cycle. In the case where an animation has implied starting values and a negative ‘animation-delay’, the starting values are taken from the moment the animation is applied.

大致意思就是:一个负的延时值是合法的。与 0s 的延时类似,它意味着动画会立即 开始播放,但会自动前进到延时值的绝对值处,就好像动画在过去已经播 放了指定的时间一样。因此实际效果就是动画跳过指定时间而从中间开始播放了。

那这就很有意思了,因为我们的动画是暂停的,所以动画的第一帧是由负的 animation-delay定义,这也将是唯一显示出的那一帧。在这个扇形上显出的比率就是我们使用animation-delay绝对值在总的动画持续时间中所占的比率。举例来说,在这里我们动画持续时间定为8s,我们只需要 animation-delay设置为 -1s,就能显示出1/8的比率。为了更加方便,我们可以设置一个长达100s的动画时间。因为animation-delay为负值时动画是永远处在暂停状态的,因此我们指定的持续时间并不会产生什么副作用。那么下面我们就写一个60%的扇形,在这里我只需要将animation-delay设为-60s,并将动画暂停即可。

CSS:

.inner-3-1 {
    position: absolute;
    right: 0;
    top: 0;
    width: 75px;
    height: 150px;
    background: inherit;
    border-radius: 0 100% 100% 0 / 0 50% 50% 0;
    transform-origin: 0 50%;
    animation: start-1 100s linear infinite, bg 50s step-end infinite;
    animation-play-state: paused;
    animation-delay: -60s
}

实例:

 

非常完美的实现了这个效果,那么在实际生产工作中,我们只需要通过JS来修改animation-delay值,就可以很轻松的控制扇形的显示比例。

当然,在这里依然需要提醒的是:部分浏览器不支持伪元素动画,或者支持得不够好,尽可能不要利用伪元素来做动画