工作的時候收到了UI的設計圖,有一個類似進度盤(話說我也不大清楚官方叫法是啥)的效果。。就想起了荒廢了好久的canvas。確認過效果,就用canvas來玩一下吧。。
UI分析
UI圖如圖:
那麼,按層級分析,裡面有幾個部分:
- 空心圓(進度條的背影)
- 空心圓(進度條)
- 空心圓(實心圓的邊框)
- 實心圓
- 文字(進度)
- 文字(最二行)
確認了需要繪製的部分起碼有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 為一個圓
圓的繪製路徑如圖所示
更詳細可參考: 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();
}
複製程式碼
讓它運動
接下來我們可以讓進度條動起來。
運動通用的有幾個點:
- 速度
speed
- 經過的路程,在這就是角度
degree
,因為是進度條,總量就是100, 這裡的圈的角度是 Math.PI*2.25 - Math.PI*0.75 = Math.PI * 1.75。那麼1%的角度就是 Math.PI*.1.5 / 100 - 我們還需要有一個變數來記錄當前運動到的百分比
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