likes
comments
collection
share

半环形进度条在起点活动中的应用

作者站长头像
站长
· 阅读数 20

前言

相信看到此文的小伙伴们,平时工作中多多少少应该会接触过进度条方面的需求,比如直角进度条、圆角进度条、圆环进度条等,大家可能也有多种方法比如利用 CSS canvas svg去实现这些需求并予以加上动画。而本次要和大家分享的就是半环形进度条如何用 canvassvg 去实现,以及如何在实现的基础上去增强用户的体验。

先来看视觉稿和最终实现

半环形进度条在起点活动中的应用 半环形进度条在起点活动中的应用

为了方便演示,本文中的案例均使用单文件的 vue +html 来做代码实例演示

Canvas 实现步骤

一、准备工作

我们首先要做一些 canvas 的基本准备工作。

半环形进度条在起点活动中的应用

先来了解一下 canvas 的画圆 arc 方法的用法和绘制角度原理。

参数描述
x圆的中心的 x 坐标。
y圆的中心的 y 坐标。
r圆的半径。
sAngle起始角,以弧度计(弧的圆形的三点钟位置是 0 度)
eAngle结束角,以弧度计。
counterclockwise可选。逆时针还是顺时针。false = 顺时针,true = 逆时针

然后我们定义一些初始变量

data() {
    return {
        canvas: null,  // canvas 实例对象
        cWidth: 750, // 预设宽度
        cHeight: 750, // 预设高度
        progress: 50, // 假设从接口获取的进度目前是 50
    }
},

接着我们在 methods 中添加一个名为 initCircleProgress 的方法,定义一些绘制所需的变量以及角度等等单位

// initCircleProgress 方法体中的代码
let radius = 124 // 外环半径
let thickness = 12 // 圆环厚度
let innerRadius = radius - thickness // 内环半径
let startAngle = -180 // 开始⾓度
let endAngle = 0 // 结束⾓度
let x = 0 // 圆⼼x坐标
let y = 0 // 圆⼼y坐标

canvas 做好初始化的准备

// html 结构
<canvas id="circleProgress"></canvas>

// initCircleProgress 方法体中的代码
this.canvas = document.getElementById('circleProgress')
let ctx = this.canvas.getContext('2d')
this.canvas.style.width = this.cWidth + 'px'
this.canvas.style.height = this.cHeight + 'px'
this.canvas.height = this.cHeight
this.canvas.width = this.cWidth
// 将绘图原点移到画布中央
ctx.translate(document.body.clientWidth / 2, document.body.clientWidth / 2)
ctx.fillStyle = '#FFF' // 初始填充颜⾊

二、绘制半圆环

我们借鉴了 canvas 绘制饼图的方法,先封装一个角度转弧度的小函数,以便之后调用

// ⾓度转弧度
function angle2Radian(angle) {
    return (angle * Math.PI) / 180
}

接下来我们先开始绘制外环,为了今后动画调用方便。我们再封装一个 renderRing 函数,它接受 2 个参数,分别是开始角度 startAngle 和结束角度 endAngle

renderRing(startAngle, endAngle)
function renderRing(startAngle, endAngle) {
  ctx.beginPath()
  // 绘制外环
  ctx.arc(0, 0, radius, angle2Radian(startAngle), angle2Radian(endAngle))
}

以上代码就可以形成一个这样的半圆饼图

半环形进度条在起点活动中的应用

/* 绘制内环 依然参考 canvas 饼图、环形图的一些技巧,通过逆时针绘制内圆形成进轨道
 * 这里的 innerRadius 内圆半径在上面定义过,所以是 radius - thickness = 12
 */
ctx.arc(0, 0, innerRadius, angle2Radian(endAngle), angle2Radian(startAngle), true) // 从-180 到 0

半环形进度条在起点活动中的应用

目前得到的半圆环,左侧和右侧是平的,而视觉稿进度条的起点和终点都是圆弧形的,所以接下来一点比较重要,需要你努力回忆起你初中时学过的三角函数知识,利用 cossin 算出 x,y 坐标后画小圆,关于 canvas 如何计算圆的坐标可以参考此文

半环形进度条在起点活动中的应用

x = Math.cos(Math.PI * 2 / 360 * 度数) * r
y = Math.sin(Math.PI * 2 / 360 * 度数) * r

现在是半圆,所以只需把 360 改成 180 即可。我们再封一个 calcRingPoint 小函数,用来计算左右两侧小圆的坐标,随后在终点处画上一个小圆,左边的同理。

// 计算圆环上点的坐标
function calcRingPoint(x, y, radius, angle) {
    let res = {}
    res.x = x + radius * Math.cos((angle * Math.PI) / 180)
    res.y = y + radius * Math.sin((angle * Math.PI) / 180)
    return res
}
// 接着绘制
function renderRing(startAngle, endAngle) {
  ...
  // 计算外环与内环终点连接处的中⼼坐标
  let oneCtrlPoint = calcRingPoint(
      x,
      y,
      innerRadius + thickness / 2,
      endAngle
  )
  // 绘制外环与内终点连接处的圆环
  ctx.arc(
      oneCtrlPoint.x,
      oneCtrlPoint.y,
      thickness / 2,
      angle2Radian(-90), 
      angle2Radian(270) // 可任意调整,只要和原来平的轨道合并成一个圆即可
  )
  // 计算外环与内环起点连接处的中⼼坐标
  let twoCtrlPoint = calcRingPoint(
      x,
      y,
      innerRadius + thickness / 2,
      startAngle
  )
  // 绘制外环与内环起点连接处的圆环
  ctx.arc(
      twoCtrlPoint.x,
      twoCtrlPoint.y,
      thickness / 2,
      angle2Radian(-90),
      angle2Radian(270)
  )
  ctx.fill()
}

半环形进度条在起点活动中的应用

经过以上步骤,我们就得到这样一个。。。有点问题的半圆环???,别急,我们需要调整一下,把绘制内环放到中间去执行,让起点处的绘制在最上层。

半环形进度条在起点活动中的应用

这下就正常了~

半环形进度条在起点活动中的应用

半环形进度条在起点活动中的应用

三、优化,提高还原度

以上的结果似乎和视觉稿还差了一点,视觉稿要比半圆更长一点,所以我们需要整体扩大一下起始角度和结束角度,一调整后我们发现原本摆正的圆被旋转了,这时候就需要再用 rotate 方法将其进行视觉摆正,如下方右图。

let startAngle = -65 // 开始⾓度
let endAngle = 155 // 结束⾓度
ctx.rotate(angle2Radian(225)) // 将画布旋转225度

半环形进度条在起点活动中的应用

半环形进度条在起点活动中的应用

最后,我们要在轨道中不停的绘制新的小圆(这里其实是整个画布都在不停重绘),模拟进度条增涨的动画效果。

// 进度条颜⾊
let progress = ctx.createLinearGradient(0, 0, 500, 0)
progress.addColorStop(0, '#1075EB')
progress.addColorStop(1, '#FFF')
ctx.fillStyle = progress
// 开始绘画
let tempAngle = startAngle
let total = 100 // 总进度
let percent = this.progress / total // 百分⽐
let twoEndAngle = percent * 220 + startAngle // 半圆原本是180,加长后是220
let step = (twoEndAngle - startAngle) / 100   // 设置步长速度
function animLoop() {
    if (tempAngle < twoEndAngle) {
        tempAngle += step
        renderRing(startAngle, tempAngle)
        window.requestAnimationFrame(animLoop)
    }
}
animLoop()

半环形进度条在起点活动中的应用

大功告成!什么?有明显的锯齿?不慌,我们再来做一个小优化。

小知识点: canvas 毕竟只是绘图接口,他就像 photoshop 一样,越放大越容易出现锯齿,曲线则更为明显。知道这个原理就好办多了,我们首先需要将图像的宽高倍率放大。

let devicePixelRatio = 4 // 定义一个设备像素倍率变量
this.canvas.height = this.cHeight * devicePixelRatio
this.canvas.width = this.cWidth * devicePixelRatio
// 再缩放抗锯齿
ctx.scale(devicePixelRatio, devicePixelRatio)

经过调整后,平滑和高清度改善许多,我们和视觉稿对比已经是高度还原了。

半环形进度条在起点活动中的应用

svg 实现步骤

一、先取一个 svg 整圆,加上圆环

介于 svg 矢量图的特性,它不会存在像 canvas 那样出现有锯齿的情况,我们只需运用好 circle 标签的两个关键属性 stroke-dasharraystroke-linecap

首先,我们可以先从网上借一个 svg 圆环图过来,做一些微调,得到下列的样子

<svg width="440" height="440" viewbox="0 0 440 440">
    <circle cx="220" cy="220" r="140" stroke-width="16" stroke="#FFF" fill="none"></circle>
    <circle cx="220" cy="220" r="140" stroke-width="16" stroke="#00A5E0" fill="none" stroke-dasharray="260 879"></circle>
</svg>

半环形进度条在起点活动中的应用

我们再来回忆一下初中数学:c=2πr 圆的周长 = 圆周率 * 2 * 半径 , 以上圆就是 3.14 * 2* 140 = 879

在这里 stroke-dasharray 表示虚线长为 260,间距为 879,以此我们可以实现以上大约四分之一进度的圆环进度条,而由于间距879(此圆的周长),所以下一段虚线我们并看不见。

二、改造 svg 整圆

我们需要的是半圆,用以上 svg 开始改造。半径为 140 的圆,周长是 140 * 2 * 3.14 = 879,一半的圆周长就是 430 左右。两头的小圆则不需要像 canvas 那样复杂,只需加上属性 stroke-linecap="round" 即可。

<svg width="440" height="440" viewBox="0 -100 440 440" class="svg-out out">
    <circle cx="180" cy="220" r="140"
            stroke-width="16"
            stroke="#FFF"
            fill="none"
            stroke-dasharray="430"
            stroke-linecap="round"
    ></circle>
</svg>

半环形进度条在起点活动中的应用

这样就得到了一个倾斜半圆环,我们再把周长加长一些,旋转后找到一个平衡点,调整到想要的效果

.out {
    width: 366px;
    transform: rotate(-200deg);
}

半环形进度条在起点活动中的应用 半环形进度条在起点活动中的应用

此时 svg 中的 stroke-dasharray 目前是 535。我们再把内圆调整成想要的颜色,调整好属性,先形成一个静态的 svg 效果

<svg width="440" height="440" viewBox="0 -100 440 440" class="svg-out out">
    <!-- 外环 -->
    <circle cx="180" cy="220" r="140"
            stroke-width="16"
            stroke="#FFF"
            fill="none"
            stroke-dasharray="535"
            stroke-linecap="round"
    ></circle>
    <!-- 内环 -->
    <circle class="inner" cx="180" cy="220" r="140" stroke-width="16" stroke="#1075EB" fill="none" stroke-dasharray="0 879" stroke-linecap="round"></circle>
</svg>

半环形进度条在起点活动中的应用

三、svg 实现动态轨道进度条

由于 svg 实现方法和 canvas 完全不一样,从 0-100 的进度值,是按照圆的周长来计算的,所以我们要把实际的进度条数值做减半处理,来显示 svg 目前所在圆环内的当前进度值。

mounted() {
    this.calcSvgProgress(this.progress) // progress: 50
}, 
methods: {
  calcSvgProgress(progress, delay = 500) {
    // 整圆c=2πr,半圆则是c=πr r=180是半圆,我们目前比半圆多一点点,所以取 170
    let percent = progress / 100, perimeter = Math.PI * 170
    setTimeout(() => {
        document.querySelector('.inner').setAttribute('stroke-dasharray', perimeter * percent + " 879");
    }, delay)
  }

别忘了用 css 加上动画过渡

.inner {
    transition: stroke-dasharray 1s;
}

经过以上代码,我们动态的把 stroke-dasharray 从 0 879,变成了 267 879,这样就能让进度条从 0 滑动到一半,到此为止,我们用 svg 实现了与 canvas 一模一样的效果,并且,他还是矢量的!

半环形进度条在起点活动中的应用

数据更新时的体验优化与差异

按照我们活动业务中的场景,用户可能在完成任务后随时能看到进度条的增涨变化或升级变化。

在这方面,如果你不做一些定制取优化体验的话,canvassvg 的表现就完全不同了。

首先来看 canvas

半环形进度条在起点活动中的应用

由于canvas 每次变化都是重绘,所以他每次都会从 0 开始绘制到最终值。

再来看 svg

半环形进度条在起点活动中的应用

由于 svg 本身就是矢量图形,所以在它发生变化时,只是进行了变形或者位移,动画也比较连贯。

假设用户升级后,发现看到的进度条由多变少,这就显得比较奇怪了。

不过,好在 svg 以及 svg 内部的标签也都属于 dom 元素,所以我们是可以用 js 对它们进行一些操控的。我们用简单的一段代码就能模拟出升级的效果

svgLevelUp() {
    let circle = document.querySelector('.inner');
    let perimeter = Math.PI * 170
    circle.setAttribute('stroke-dasharray', perimeter + "  879");
    setTimeout(() => {
        circle.style.display = 'none'
        circle.setAttribute('stroke-dasharray', '0 879')
        setTimeout(() => {
            circle.style.display = 'block'
            this.calcSvgProgress(25, 100)
        }, 10)
    }, 1000)
}, 

原理很简单:先把 stroke-dasharray 改成满值,做个延时 → 内环元素暂时 display:none ,同时改回空(0,879)再延时 → 重新让内环 display:block ,此时再拉接口获取最新 progress 进度值,来达到一个让用户看到升级后,进度又涨到 25% 进度的效果。

半环形进度条在起点活动中的应用

总结

canvas 实现过程较为复杂,但可以自由调整细节,比较容易应付多变的需求,整个进度条 0-100 值和实际数值相匹配。

svg 实现过程简单,代码量也比较少,按照 stroke-dasharray 的特性,若需求较为复杂时,可能在视觉还原度上会有一些难度,并有一定的局限性,需要自行再添加方法优化体验,整个进度条 0-100 的值是按照整圆来计算的,若半圆则需要减半做换算处理。

没有最好的方案,只有最适合的方案。以上就是本次分享所带来的所有内容了,希望能给屏幕前的小伙伴们开拓一些思路,感谢阅读。

转载自:https://juejin.cn/post/7186917605213470776
评论
请登录