08月23, 2016

CSS3环形进度条

前阵子,同事问了我一个问题,下面的图该怎么来实现?

css3进度条

当时因为项目忙,加上总感觉这个很简单,所以也就马马虎虎和同事说了一句,这个实现不难吧?用圆角什么的,随便就能搞定了。

回过头来细想,这个还是需要一些css功底的。

我一开始想到的是html5的progress标签,可惜它无法自定义样式。

在网上搜索了一下,实现CSS3环形的文章挺多:

基于上面的文章,我们完全可以抄一抄,但还是需要了解整个过程。

HTML结构

<div class="progress-ring">
    <div class="progress-track"></div>
    <div class="progress-left"></div>
    <div class="progress-right"></div>
    <div class="progress-cover"></div>
    <div class="progress-text">
        <span class="progress-num">0</span>
        <span class="progress-percent">%</span>
    </div>
</div>

先抛开progress-text这一层,这个实现原理大概是这样的:

  • progress-track是整个大饼
  • progress-left、progress-right、progress-cover,三个左大饼(左半边)
  • 将progress-right旋转180度,变成右大饼(右半边),透明度为0
  • 进度条相当于progress-left向右旋转角度,此时progress-cover死死地把左边给压死
  • 当进度条超过50%时,让遮罩隐藏,右边的半圆环显示出来,左边的半圆环继续旋转

sass代码

@charset "UTF-8";

$size: 130px; // 圆环宽高
$borderWidth: ($size / 8); // 轨道宽度
$fontColor: #949494; // 文字颜色
$trackColor: #f0f0f0; //轨道颜色
$progressColor: #6ec84e; //进度条颜色

@keyframes toggle {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}

@mixin positioning($pos: absolute) {
    position: $pos;
    top: 0;
    left: 0;
    height: $size;
    width: $size;
}

.progress-ring {
    @include positioning(relative);

    div {
        @include positioning; /* 圆环宽高,可自定义 */
        border: $borderWidth solid transparent;
        border-radius: 50%;
        box-sizing: border-box;
    }
    .progress-left, .progress-right, .progress-cover {
        clip: rect(0 ($size / 2) $size 0); /* rect( [0 | <width/2> | <height> | 0] ) */
    }

    .progress-track, .progress-cover {
        border-color: $trackColor;
    }

    .progress-left, .progress-right {
        border-color: $progressColor;
    }

    .progress-right {
        opacity: 0;
        transform: rotate(180deg);
    }
    .progress-text {
        border: none;
        font-family: "Arial", sans-serif;
        text-align: center;
        font-size: ($size / 4);
        color: $fontColor; /* 进度文字颜色 */
        line-height: $size;
    }
    .progress-percent {
        font-size: .55em;
        font-style: italic;
        font-family: "Microsoft Yahei", sans-serif;
    }
}

scss编译可以通过webstorm,或者命令行(装了ruby,且gem install sass):

sass --watch page.scss:page.css

当然你也可以使用koala,说实话在项目中一般很少用了。

通过HTML结构和CSS,我们可以得到下面的结果:

DEMO1

随后我们人为地给DIV(progress-left)加点css:

element.style {
    transform: rotate(10deg);
}

就可以看到以下结果:

DEMO2

设置进度

可以通过以下的js来实现:

 function setProgress(percent){

    var $contain = document.querySelector(".progress-ring"),

        $left = $contain.querySelector(".progress-left");

    $left.style.WebkitTransform = "rotate(" + percent * 3.6 + "deg)";

    //加过渡时间
    $left.style.WebkitTransition = "-webkit-transform 1500ms linear";

}

旋转的角度,是通过百分比来实现的,当百分百(100)的时候,整个圆是360度,所以进度为1的时候,是3.6度。

我们打开控制台,执行:

setProgress(20)

是非常OK的。

但是当传入60时,就有问题了。这个问题在上面也提及了,超过50的做法。

技术难点

旋转过半的时候,DIV(progress-cover)消失的实现。

利用的是 CSS3 animation 的 timing-function属性

@keyframes toggle {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
.progress-right {
    opacity: 1;
    animation: toggle($duration * 50 / $precent) step-end;
}
.progress-cover {
    opacity: 0;
    animation: toggle($duration * 50 / $precent) step-start;
}

这里需要注意的是animate的时间算法,关于step-start和step-end,可以参考这篇文章

所以js只需要简单处理一下即可。

function setProgress(percent){

    var $contain = document.querySelector(".progress-ring"),

        $left = $contain.querySelector(".progress-left"),

        $right = $contain.querySelector(".progress-right"),

        $cover = $contain.querySelector(".progress-cover");

    $left.style.WebkitTransform = "rotate(" + percent * 3.6 + "deg)";

    //加过渡时间
    $left.style.WebkitTransition = "-webkit-transform 1500ms linear";

    if (percent > 50) {

        var animation = "toggle " + (1500 * 50 / percent) + "ms";

        $right.style.opacity = "1";
        $right.style.animation = animation;
        $right.style.animationTimingFunction = "step-end";

        $cover.style.opacity = "0";
        $cover.style.animation = animation;
        $cover.style.animationTimingFunction = "step-start";

    }
}

我们再打开控制台,输入:

setProgress(60)

就OK了。

结语

真的OK了吗?

NO。起码还有几个问题。

1.只考虑了webkit内核的浏览器,当然这个简单,代码改写一下就行。

2.在现有的进度条基础上,即我调用了setProgress(60)之后,再调用一次,就会有问题,解决的方法很简单,进来的时候,只要把这些style清空掉即可。(但不是太合理,万一还有其他的style样式,一并给干掉了)

3.当有多个进度条的时候,怎么处理?针对这一问题,可以封装jQuery插件,但现在已经是mvvm的年代了,所以可以将上面的代码封装成一个指令,在react中是一个组件。

本文链接:www.my-fe.pub/post/css-record-3.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。