背景:
一日晚上下班的我靜靜的靠在角落上聽著歌瞧预,這時(shí)"滴!滴!"手機(jī)上傳來一陣qq消息。原來我人在問王者榮耀的雷達(dá)圖在頁面上如何做出來的汗唱,有人回答用canvas繪畫屡拨。那么問題來了攻冷,已經(jīng)好久沒有使用canvas繪畫了東西娃胆。
SO,就想自己畫一個(gè)canvas雷達(dá)圖等曼,順便重新回顧一下canvas的知識(shí)點(diǎn)里烦。
王者榮耀雷達(dá)圖的基本構(gòu)成。
聊天記錄當(dāng)中的雷達(dá)圖不是特別清楚禁谦,所以我這邊截圖了自己的一個(gè)戰(zhàn)績雷達(dá)圖胁黑。
是不是有被我的戰(zhàn)績嚇到了,害不害怕州泊!
好了扯遠(yuǎn)了丧蘸,讓我們回到正題上來。
通過截圖上面的雷達(dá)圖基本主體是一個(gè)正六邊形遥皂,每個(gè)頂點(diǎn)則配有相應(yīng)的文字說明力喷。
然后就是中間紅色區(qū)域部分則由對(duì)角線上的點(diǎn),連成一圈填充構(gòu)成演训。因此這里我們稱它為數(shù)據(jù)填充區(qū)
所以這個(gè)雷達(dá)圖我們分為三步來完成弟孟。
①正六邊形
②數(shù)據(jù)填充區(qū)
③繪制文本
正六變形的坐標(biāo)點(diǎn)解析
在繪畫這個(gè)正六邊形的時(shí)候,先讓我們對(duì)于這個(gè)正六邊形進(jìn)行簡單的數(shù)學(xué)分析样悟。
這里先用畫板畫一個(gè)正六變形拂募,然后進(jìn)行切割并切角。
是吧窟她,借用以前高中還是初中的數(shù)學(xué)陈症,正六邊形的內(nèi)角和720°
,那么每一個(gè)對(duì)角就是120°
礁苗。在已知對(duì)角線的長度爬凑。那么通過sin60°
徙缴,cos60°
一類的试伙,那個(gè)可以求出各個(gè)三角形的邊長嘁信。
可是問題來了,這里我們要計(jì)算的是各個(gè)坐標(biāo)點(diǎn)疏叨。而canvas的坐標(biāo)軸是從左上角算(0潘靖,0)原點(diǎn)的單象限坐標(biāo)軸。假設(shè)六邊形的中心點(diǎn)是(250蚤蔓,250)卦溢、對(duì)角線的長度是100*2,那么按照三角函數(shù)推斷:
bottom-center
坐標(biāo):(250, 250 + 100)
bottom-left
坐標(biāo):(250 - 100*sin(60°), 250+100*cos(60°))
top-left
坐標(biāo):(250 - 100*sin(60°), 250-100*cos(60°))
top-center
坐標(biāo):(250, 250 - 100)
top-right
坐標(biāo):(250 + 100*sin(60°), 250-100*cos(60°))
bottom-right
的坐標(biāo):(250 + 100*sin(60°), 250+100*cos(60°))
坐標(biāo)是出來了秀又,但是一個(gè)點(diǎn)一個(gè)點(diǎn)去繪畫是不是有點(diǎn)太low了单寂!
腫么辦?
啦啦啦啦吐辙!
那么就到了我們找規(guī)律的時(shí)間來了宣决!
但是在找規(guī)律的同時(shí),為毛中心點(diǎn)的X
軸和別人不一樣昏苏,為毛一會(huì)加一會(huì)減尊沸。
所以當(dāng)思考各坐標(biāo)點(diǎn)參數(shù)的規(guī)律的時(shí)候,讓先回顧以前的函數(shù)角度圖表
看完這個(gè)函數(shù)參照?qǐng)D之后贤惯,讓我再次修改一下6個(gè)點(diǎn)的書寫方式洼专。
bottom-center
坐標(biāo):(250 + 100*sin(0°), 250 + 100*cos(0°))
bottom-left
坐標(biāo):(250 + 100*sin(300°), 250+100*cos(300°))
top-left
坐標(biāo):(250 + 100*sin(240°), 250-100*cos(240°))
top-center
坐標(biāo):(250 +100*sin(180°), 250 + 100*cos(180°))
top-right
坐標(biāo):(250 + 100*sin(120°), 250-100*cos(120°))
bottom-right
的坐標(biāo):(250 + 100*sin60°), 250+100*cos(60°))
這個(gè)時(shí)候再看組坐標(biāo)數(shù)據(jù)點(diǎn),是不是感覺有點(diǎn)意思孵构!
那么這個(gè)時(shí)候我們便可以通過一個(gè)for循環(huán)屁商,用一個(gè)數(shù)組把這6個(gè)坐標(biāo)點(diǎn)給記錄下來。
var pointArr = [];
for (var i = 0; i < 6; i++) {
pointArr[i] = {};
pointArr[i].x = 250 + 100 * Math.sin(60 * i);
pointArr[i].y = 250 + 100* Math.cos(60 * i);
}
1.1 繪畫正六邊形
前面既然浦译,將正六邊形的坐標(biāo)點(diǎn)通過一個(gè)for循環(huán)解析出來棒假。那么就是代碼繪畫正六邊形了:
<style>
canvas {
display: block;
width: 500px;
height: 500px;
}
</style>
<body>
<canvas class="radar"></canvas>
</body>
<script>
var canvas = document.getElementsByClassName('radar')[0];
canvas.width = 500;
canvas.height = 500;
var ctx = canvas.getContext('2d');
ctx.save();
ctx.strokeStyle = '#888888'; // 設(shè)置線條顏色
var lineArr = [];
var rAngle = Math.PI * 2 / 6; // 算出每一個(gè)內(nèi)角和
console.log(rAngle);
var rCenter = 250; // 確定中心點(diǎn)
var curR = 100; // 確定半徑長度
ctx.beginPath();
for (var i = 0; i < 6; i++) {
lineArr[i] = {};
lineArr[i].y = rCenter + curR * Math.cos(rAngle * i);
lineArr[i].x = rCenter + curR * Math.sin(rAngle * i);
ctx.lineTo(lineArr[i].x, lineArr[i].y);
}
ctx.closePath();
ctx.stroke();
ctx.restore();
啦啦啦!>选帽哑!一個(gè)正六邊形就這么的畫出來。
備注:這里rAngle這里是很靈活的叹俏,如果說畫18正邊形妻枕,就除以18,然后for循環(huán)18次就ok了.
哈哈U吵邸屡谐!感覺發(fā)現(xiàn)了新大陸了!繪制正多邊形的貌似可以按照這個(gè)規(guī)律來r蚴愕掏!
1.2 繪畫對(duì)角線
既然前面有一個(gè)數(shù)組存儲(chǔ)各個(gè)坐標(biāo)點(diǎn),所以讓每個(gè)對(duì)角線對(duì)角點(diǎn)直線想連就ok了顶伞!
ctx.strokeStyle = '#e8ddc7'; // PS吸管那么一吸
ctx.save();
ctx.beginPath();
// for (var j = 0; j < 3; j++) {
// ctx.lineTo(lineArr[j].x, lineArr[j].y);
// ctx.lineTo(lineArr[j+3].x, lineArr[j+3].y);
// ctx.stroke();
// }
for (var j = 0; j < 3; j++) {
ctx.moveTo(lineArr[j].x, lineArr[j].y);
ctx.lineTo(lineArr[j + 3].x, lineArr[j + 3].y);
ctx.stroke();
}
ctx.closePath();
ctx.restore();
2.1數(shù)據(jù)填充區(qū)
關(guān)于數(shù)據(jù)填充區(qū)饵撑,也就是雷達(dá)圖當(dāng)中剑梳,不規(guī)則的紅色半透明的六邊形。其實(shí)就是就可以看做中心點(diǎn)滑潘,到各個(gè)邊角點(diǎn)之間線段為一區(qū)間這垢乙。之后就是將這個(gè)區(qū)間分成若干份,你占這個(gè)這個(gè)區(qū)間多少份语卤,滿份就是邊角點(diǎn)追逮,零份就是原點(diǎn)。
觀察前面的雷達(dá)圖當(dāng)中粹舵,B等級(jí)大概占據(jù)某個(gè)等級(jí)的50%左右钮孵。而B前面還有等級(jí)A、S眼滤。
所以當(dāng)S等級(jí)時(shí)候油猫,可以看作區(qū)間 / 1。
B等級(jí)看作區(qū)間 / 2, 那么A就是 區(qū)間 / 1.5.
以此類推就可以得出剩下 C 就是區(qū)間 / 2.5柠偶、D:區(qū)間/ 3
這里我就不用for循環(huán)書寫了情妖,直接偷懶手寫一個(gè)對(duì)象。
// 繪制數(shù)據(jù)區(qū)域
var letterData = {
'S': 1,
'A': 1.5,
'B': 2,
'C': 2.5,
'D': 3
}
ctx.save();
ctx.beginPath();
for (var i = 0; i < 6; i++) {
lineArr[i].yEnd = rCenter + curR * Math.cos(rAngle * i) / (letterData[rData[i][1]]);
lineArr[i].xEnd = rCenter + curR * Math.sin(rAngle * i) / (letterData[rData[i][1]]);
ctx.lineTo(lineArr[i].xEnd, lineArr[i].yEnd);
console.log(lineArr);
}
ctx.closePath();
ctx.stroke();
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.fill();
2.2 對(duì)數(shù)據(jù)填充區(qū)域繪畫小圓點(diǎn)和邊長
當(dāng)我們回歸到前面的截圖發(fā)現(xiàn)诱担,需要單獨(dú)把數(shù)據(jù)填充區(qū)域的的各個(gè)點(diǎn)位置給加強(qiáng)毡证,并把邊角用更深的線條的描繪出來。
ctx.lineWidth = 2; //設(shè)置數(shù)據(jù)填充區(qū)域的線條顏色
ctx.strokeStyle = '#dd3f26'; //設(shè)置填充區(qū)域的顏色
var point = 3; //設(shè)置數(shù)據(jù)填充區(qū)域的小圓點(diǎn)大小
for (var i = 0; i < 6; i++) {
ctx.beginPath();
ctx.arc(lineArr[i].xEnd, lineArr[i].yEnd, point, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 0, 0, 0.8)';
ctx.fill();
console.log(lineArr);
}
ctx.restore();
3.1 繪制文本
王者榮耀雷達(dá)文本是需要繪制兩點(diǎn)蔫仙,
①用黑色16px字體繪制各點(diǎn)描述點(diǎn)
②用紅色30px字體繪制各點(diǎn)能力級(jí)別
但是估計(jì)看到繪制文本料睛,估計(jì)有的小伙伴就會(huì)說。不是有數(shù)組的存儲(chǔ)各個(gè)邊角的坐標(biāo)摇邦,直接一個(gè)for循環(huán)依次根據(jù)各個(gè)點(diǎn)繪畫出來不就OK了恤煞。
// 繪制文本
var rData = [
['生存', 'S'],
['經(jīng)濟(jì)', 'S'],
['輸出', 'S'],
['KDA', 'B'],
['打野', 'B'],
['推進(jìn)', 'S']
]
ctx.save();
ctx.font = '16px Microsoft Yahei'; //設(shè)置字體
ctx.fillStyle = '#000'; // 顏色
for (var i = 0; i < 6; i++) {
var y = rCenter + curR * Math.cos(rAngle * i);
var x = rCenter + curR * Math.sin(rAngle * i);
ctx.fillText(rData[i][0], x, y);
}
ctx.restore();
瀏覽器最終顯示的視覺效果:
是不是覺得很驚喜,這里輸出
施籍、經(jīng)濟(jì)
位置勉強(qiáng)還行居扒,但是剩下的文字位置就偏差了許多了。所以在繪制文字的時(shí)候丑慎,還得針對(duì)文字的坐標(biāo)位置進(jìn)行相應(yīng)的調(diào)整喜喂。
3.2 繪制文本--描述
既然直接調(diào)用坐標(biāo)的位置會(huì)出問題,那么讓根據(jù)上文中的圖片文字的規(guī)則簡單分析竿裂。
①如果X軸
== 中心點(diǎn)玉吁,那么就判斷Y軸
。比中心點(diǎn)大文字下移一點(diǎn)腻异,反之文字上移一點(diǎn)进副。
②如果X軸
< 中心點(diǎn),那么文字X軸位置就左移動(dòng)一點(diǎn),反正右移動(dòng)距離悔常。
// 繪制文本
ctx.save();
var fontSize = 16;
ctx.font = fontSize + 'px Microsoft Yahei';
ctx.textBaseline="middle"; //設(shè)置基線參考點(diǎn)
ctx.textAlign="center"; // 文本居中
ctx.fillStyle = '#000';
for (var i = 0; i < 6; i++) {
var y = rCenter + curR * Math.cos(rAngle * i);
var x = rCenter + curR * Math.sin(rAngle * i);
console.log(Math.sin(rAngle * i))
var s_width = ctx.measureText(rData[i][0]).width; //獲取當(dāng)前繪畫的字體寬度
if ( x == rCenter) {
if (y > rCenter ) {
ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
} else {
ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
}
} else if ( x > rCenter) {
console.log(rData[i][0]);
ctx.fillText(rData[i][0], x + s_width*1.5, y);
} else {
ctx.fillText(rData[i][0], x - s_width*1.5, y);
}
這里多了好幾個(gè)不常用的屬性影斑,下面就是介紹這些屬性的特點(diǎn):
ctx.textBaseline
: 設(shè)置或返回在繪制文本時(shí)使用的當(dāng)前文本基線
說到基線曾沈,各位童鞋想一想咱們以前英文練習(xí)本,上面有著一條條線條
瞬間回憶到當(dāng)年被罰抄英語單詞的歲月鸥昏,一把辛酸淚呀。
網(wǎng)頁設(shè)計(jì)字體也有一個(gè)基線的存在姐帚,因此canvas的基線點(diǎn)就是直接從坐標(biāo)點(diǎn)劃出一條橫線基線吏垮。
這里從網(wǎng)絡(luò)上截圖一張,通過設(shè)置基線參考位置罐旗,看看文本所在位置的改變膳汪。
ctx.textAlign
: 這個(gè)文本水平居中,不過和CSS當(dāng)中的居中不一樣的是九秀,他是從坐標(biāo)點(diǎn)劃出一條豎線分割文本的遗嗽。
ctx.measureText
: 返回包含指定文本寬度的對(duì)象。
通俗一點(diǎn)的就是說鼓蜒,就是獲取你繪制文本的寬度痹换。假設(shè)一排文字內(nèi)容為'Hello World', size為16px大小文本都弹。在這里高度都是16px穩(wěn)定不變娇豫,這樣canvas畫其他元素對(duì)這個(gè)位置只需要Y軸
移動(dòng)這個(gè)文本的'size'大小就可以避免覆蓋到上面。
但是如果要X
軸去移動(dòng)位置,你根本不知道'Hello World'這串文本的長度畅厢。那么這個(gè)時(shí)候就需要ctx.measureText這個(gè)方法冯痢,獲取當(dāng)前你繪制文本的寬度。
3.2 繪制文本--能力級(jí)別
既然前面已經(jīng)介紹了描述的繪畫方法框杜,那么依葫蘆畫瓢浦楣。讓我們一并開始繪制能力級(jí)別的文本。
// 繪制文本
ctx.save();
var fontSize = 16;
var maxfontSize = 30;
ctx.font = fontSize + 'px Microsoft Yahei';
ctx.textBaseline="middle";
ctx.textAlign="center";
for (var i = 0; i < 6; i++) {
var y = rCenter + curR * Math.cos(rAngle * i);
var x = rCenter + curR * Math.sin(rAngle * i);
console.log(Math.sin(rAngle * i))
var s_width = ctx.measureText(rData[i][0]).width;
if ( x == rCenter) {
if (y > rCenter ) {
ctx.fillText(rData[i][0], x - s_width/2, y + fontSize);
} else {
ctx.fillText(rData[i][0], x - s_width/2, y - fontSize);
}
} else if ( x > rCenter) {
console.log(rData[i][0]);
ctx.fillText(rData[i][0], x + s_width*1.5, y);
} else {
ctx.fillText(rData[i][0], x - s_width*1.5, y);
}
}
ctx.restore();
ctx.save();
// 繪制等級(jí)
ctx.font = '30px Microsoft Yahei bold';
ctx.fillStyle = '#d7431f';
ctx.textBaseline="middle";
ctx.textAlign="center";
for (var i = 0; i < 6; i++) {
var y = rCenter + curR * Math.cos(rAngle * i);
var x = rCenter + curR * Math.sin(rAngle * i);
var M_width = ctx.measureText(rData[i][1]).width;
if ( x == rCenter) {
if (y > rCenter ) {
ctx.fillText(rData[i][1], x + M_width/2, y + fontSize);
} else {
ctx.fillText(rData[i][1], x + M_width/2, y - fontSize);
}
} else if ( x > rCenter) {
console.log(rData[i][0]);
ctx.fillText(rData[i][1], x + M_width, y);
} else {
ctx.fillText(rData[i][1], x - M_width, y);
}
}
ctx.restore();
ctx.save();
頁面最終效果:
結(jié)尾
好了咪辱!以上就是鄙人對(duì)于canvas繪畫一點(diǎn)簡單理解與復(fù)習(xí)了振劳,其中也回顧了一些canvas基本屬性點(diǎn)。后續(xù)如何用canvas玩出各種花樣就看各位看官自己了油狂!
小貼士:
在使用ctx.measureText
這個(gè)方法的時(shí)候需要注意一下澎迎。這個(gè)方法在寬度參考對(duì)象也跟當(dāng)前繪畫環(huán)境的font-size有關(guān)聯(lián)的。
打個(gè)比方說选调,在繪制描述的文本的時(shí)候夹供。font-size設(shè)置是16px,那么ctx.measureText('輸出').width 是32仁堪。
那么在繪制能力等級(jí)的時(shí)候哮洽,font-size設(shè)置是32,那么ctx.measureText('輸出').width 就不再是32了而是64或者弦聂。
原創(chuàng)文章鸟辅,文筆有限氛什,才疏學(xué)淺,文中若有不正之處匪凉,再次再次再次歡迎各位啪啪的打臉賜教枪眉。(有句話說的好,重要的詞得說三遍再层。)
PS:對(duì)于里面完整blogDemo代碼感興趣的贸铜,可以從本人github上閱覽。
源碼demo傳送門地址
我是車大棒聂受!我為我自己帶眼了蒿秦!