用canvas畫一個進度盤

Suven發表於2018-06-17

工作的時候收到了UI的設計圖,有一個類似進度盤(話說我也不大清楚官方叫法是啥)的效果。。就想起了荒廢了好久的canvas。確認過效果,就用canvas來玩一下吧。。

UI分析

UI圖如圖:

image

那麼,按層級分析,裡面有幾個部分:

  1. 空心圓(進度條的背影)
  2. 空心圓(進度條)
  3. 空心圓(實心圓的邊框)
  4. 實心圓
  5. 文字(進度)
  6. 文字(最二行)

確認了需要繪製的部分起碼有6塊。

開始繪製

canvas瞭解

根據上面的各層級需求,大概可能會使用上的api如下

// 線寬
ctx.lineWidth
// 用於指定結束線帽的樣式
ctx.lineCap
// 繪製漸變色
ctx.createLinearGradient
// 指定線條繪圖顏色
ctx.strokeStyle
// 指定填充繪圖顏色
ctx.fillStyle
// 建立一個圓
ctx.arc()
// 繪製文字
ctx.fillText()
// 指定文字字樣
ctx.font
// 指定文字位置
ctx.textAlign
複製程式碼

canvas畫圓說明

就是ctx.arc(x, y, redius, startAngle, endAngle, counterclockwise)這個方法可以建立一個圓形,接收6個引數,分別為 圓心x座標,圓心y座標, 圓半徑,起始弧度,結束弧度,是否逆時針繪製

角度=180°×弧度÷π ,弧度=角度×π÷180°

一個完整圓為: 360° × π ÷ 180° = 2 × π

綜上,從0開始到 Math.PI*2 為一個圓

圓的繪製路徑如圖所示

image

更詳細可參考: W3school

先宣告一個canvas

// HTML部分
<canvas id="canvas"></canvas>
// JS部分
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
canvas.width = Math.ceil(300 / 1920 * window.innerWidth);
canvas.height = Math.ceil(300 / 1920 * window.innerWidth);
複製程式碼

建立一個類

class Annulus {

    constructor(obj={}){
        this.size = 100;
        this.lineWidth = 10;
        this.location = obj.location || {x: canvas.width/2, y: canvas.height/2};
    }

    // 繪製進度條底層
    drawBg(){}
    // 繪製進度條
    drawCircleLay(){}
    // 繪製中心的大圓
    drawCenterCircle(){}
    // 繪製大圓邊緣的邊
    drawCenterBorderCircle(){}
    // 繪製進度文字
    drawTextPercent(){}
    // 繪製進度下面的文字 
    drawTextName(){}
    // 定義動畫
    animate(){
        this.drawBg();
        this.drawCircleLay();
        this.drawCenterCircle();
        this.drawCenterBorderCircle();
        this.drawTextPercent();
        this.drawTextName();
    }
    // 執行
    run(){
        this.animate();
    }
}
複製程式碼

進度條的背景

drawBg(){
    // 繪製背景圈
    ctx.beginPath();
    ctx.strokeStyle = '#2d4264';
    ctx.lineWidth = 10;
    ctx.lineCap = "round";
    ctx.arc(this.location.x, this.location.y, 100, Math.PI*0.75, Math.PI*2.25, false);
    ctx.stroke();
}
複製程式碼

進度條

drawCircleLay(){
    // 繪製進度條
    ctx.beginPath();
    var gradient = ctx.createLinearGradient(0, this.linearLocation().start, 0, this.linearLocation().end);
    gradient.addColorStop(0, '#0f6cd9');
    gradient.addColorStop(1, '#05a6da');
    ctx.strokeStyle = gradient;
    ctx.lineWidth = this.lineWidth;
    ctx.lineCap = "round";
    ctx.arc(this.location.x, this.location.y, this.size, Math.PI*0.75, Math.PI*2.25, false);
    ctx.stroke();
}

linearLocation(){
    // 設定漸變背影的起始結束點
    let start = this.location.y - ((this.size-15)*2 + this.lineWidth)/2;
    let end = start + (this.size-15)*2 + this.lineWidth
    return {start: start, end: end}   
}
複製程式碼

目前基本上跟背景圈一樣,不同的是這裡用了漸變色,先繪製出來,後再給加上變數變化

中心圓

drawCenterCircle(){
    // 繪製中心圓
    ctx.beginPath();
    var gradient = ctx.createLinearGradient(0, this.linearLocation().start, 0, this.linearLocation().end);
    gradient.addColorStop(0, '#39a8ce');
    gradient.addColorStop(1, '#5647c9');
    ctx.fillStyle = gradient;
    ctx.arc(this.location.x, this.location.y, this.size-15, 0, Math.PI*2, false);
    ctx.fill();
}
複製程式碼

中心圓的邊

drawCenterBorderCircle(){
    // 繪製中心圓周邊的那圈
    ctx.beginPath();
    ctx.strokeStyle = 'rgba(0,0,0,0.3)';
    ctx.lineWidth = 10;
    ctx.arc(this.location.x, this.location.y, this.size-20, 0, Math.PI*2, false);
    ctx.stroke();
}
複製程式碼

繪製進度

drawTextPercent(percent){
    // 繪製進度文字
    ctx.beginPath();
    ctx.font = '31px Arial';
    ctx.textAlign="center";
    ctx.fillStyle="#192f47";
    ctx.fillText(`100%`, this.location.x, this.location.y);
    ctx.stroke();
}
複製程式碼

繪製進度下面的文字

drawTextName(){
    // 繪製二級文字
    ctx.beginPath();
    ctx.font = '14px "Microsoft YaHei"';
    ctx.textAlign="center";
    ctx.fillStyle="#192f47";
    ctx.fillText('text', this.location.x, this.location.y+25);
    ctx.stroke();
}
複製程式碼

讓它運動

接下來我們可以讓進度條動起來。
運動通用的有幾個點:

  1. 速度speed
  2. 經過的路程,在這就是角度degree,因為是進度條,總量就是100, 這裡的圈的角度是 Math.PI*2.25 - Math.PI*0.75 = Math.PI * 1.75。那麼1%的角度就是 Math.PI*.1.5 / 100
  3. 我們還需要有一個變數來記錄當前運動到的百分比 tol

綜上,我們把 類的 constructor 改造一下,如下:

constructor(obj = {}){
    /*
    * speed -- 速度
    * color -- 顏色
    * size -- 大小
    * lineWidth -- 線寬
    * location -- 圓心位置
    * text -- 文字
    * value -- 圓環滾動的值 ,這裡指是百分比
    */
    this.speed = obj.speed || 0.1;
    this.color = obj.color || '#ffeedd';
    // 180 是UI的大體尺寸是在180px,以1920為基準
    this.size = Math.ceil((obj.size || 80) * (canvas.width / 180));
    this.lineWidth = obj.lineWidth || 10;
    this.location = obj.location || {x: canvas.width/2, y: canvas.height/2};
    this.textName = obj.text || '第二行文字title';
    this.value = obj.value || 0;
    // 這裡是圓的終點減去圓的起點
    this.degree = Math.PI*1.5/100;
    this.animate = this.animate.bind(this);
    this.tol = 0;
}
複製程式碼

drawCircleLay()方法的繪製結束點使用變數來控制

drawCircleLay(){
    // 繪製進度條
    if (this.value == 0) return;
    ctx.beginPath();
    var gradient = ctx.createLinearGradient(0, this.linearLocation().start, 0, this.linearLocation().end);
    gradient.addColorStop(0, '#0f6cd9');
    gradient.addColorStop(1, '#05a6da');
    ctx.strokeStyle = gradient;
    ctx.lineWidth = this.lineWidth;
    ctx.lineCap = "round";
    //這裡改成用變數來控制 
    ctx.arc(this.location.x, this.location.y, this.size, Math.PI * 0.75, Math.PI*0.75+this.tol * this.degree, false);
    ctx.stroke();
}
複製程式碼

drawTextPercent() 可以把變數tol傳進來作用當前顯示的進度數

drawTextPercent(percent){
    // 繪製進度文字
    ctx.beginPath();
    ctx.font = `${this.size / 2.5}px Arial`;
    ctx.textAlign="center";
    ctx.fillStyle="#192f47";
    ctx.fillText(`${parseInt(percent)}%`, this.location.x, this.location.y);
    ctx.stroke();
}
複製程式碼

animate() 方法加上 RAF, 讓其運動

animate(){
    window.requestAnimationFrame(this.animate);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    this.drawBg();
    this.drawCircleLay();
    this.drawCenterCircle();
    this.drawCenterBorderCircle();
    this.drawTextPercent(this.tol);
    this.drawTextName(this.textName);
    if (this.tol < this.value) { this.tol += this.speed }
}
複製程式碼

至此,一個進度盤已完成。其實還有很多改進空間,比如更合理的封裝,監聽window.resize的時候進行大小的改變等。。

例項地址: codepen

相關文章