前言
昨天公司培訓canvas相關內容,然後培訓完還留下一道homework,覺得挺有意思的,特來與大家分享分享。大家可以先不看我的實現,自己嘗試試試,還是可以學到不少知識的。
題目內容
初看題目內容好像挺簡單的,不就是個漸變嘛,看我的,翻翻萬能的mdn查查canvas漸變api,
CanvasGradient
介面表示描述漸變的不透明物件。通過CanvasRenderingContext2D.createLinearGradient()
或CanvasRenderingContext2D.createRadialGradient()
的返回值得到. developer.mozilla.org/zh-CN/docs/…
好像哪不對,這兩個漸變api只有線性漸變(LinearGradient
)和圓形漸變(RadialGradient
);而題目的意思是繪製一個扇形漸變,從0到360度的一個按照角度漸變的一個圓。然後我就問我們設計的小夥伴,怎麼畫這種圓錐漸變,畢竟工具畫圖和程式碼畫圖思路還是一樣的,只不過過程不一樣。然而現實是,ps自帶角度漸變。
what?好吧,只能自己分析了。
分析題目
首先拋開漸變不談,我們把顏色分成幾塊,每塊一種顏色是不是就是我們熟悉的餅圖。
那麼我們運用微分的思想,把圓分成更多份的扇形,每種扇形一個顏色是不是就能實現題目的效果呢?我們來試試。
漸變色的實現
根據我們分析的思路,首先我們先從顏色等份開始做起,顏色常見的表示有四種十六進位制顏色值(#000000),RGBA,HSL和HSV。
- HSL:H(hue)色相,S(saturation)飽和度,以及L(lightness)亮度
- HSV:H(hue)色相,S(saturation)飽和度,以及V(value)色調
- RGBA:Red(紅色)Green(綠色)Blue(藍色)和Alpha的色彩空間
色相(Hue):取值範圍是從0°到360°正上方為0°的話,0度為R(紅)色,120度為G(綠)色,240度為B(藍)色
因此其實這個題目我認為用這個顏色值是最好的,算出來的漸變比較好看,不過這裡我使用的是RGBA。感興趣的小夥伴可以嘗試用HSV寫個漸變演算法,用過角度變換。
亮度(lightness):最下面是0%也最暗,最上面是100%,最亮
飽和度(saturation):和亮度一樣也是通過百分比表示的。
這些作為補充知識,這裡我是使用的RGBA顏色。竟然顏色需要等分,那麼我把顏色轉換成RGBA,然後等分RGB三種顏色,每一份取三種顏色的差值的
/**
*
* @param startColor 指定起始顏色
* @param endColor 指定結束顏色
* @param step 劃分漸變色區域數量
* @returns {Array} 返回漸變色陣列
*/
let gradientColor = function(startColor, endColor, step) {
let startRGB = this.colorRgb(startColor); //轉換為rgb陣列模式
let startR = startRGB[0];
let startG = startRGB[1];
let startB = startRGB[2];
let endRGB = this.colorRgb(endColor);
let endR = endRGB[0];
let endG = endRGB[1];
let endB = endRGB[2];
let sR = (endR - startR) / step; //總差值
let sG = (endG - startG) / step;
let sB = (endB - startB) / step;
let colorArr = [];
for (let i = 0; i < step; i++) {
//計算每一步的hex值
let hex = this.colorHex('rgb(' + parseInt((sR * i + startR)) + ',' + parseInt((sG * i + startG)) + ',' +
parseInt((sB * i + startB)) + ')');
colorArr.push(hex);
}
return colorArr;
};
複製程式碼
我們把相應的十六進位制顏色轉換成RGB然後根據起始顏色和末顏色,計算出差值,即每份的顏色值。得出的陣列就是梯度顏色的陣列。相應的顏色裝換函式如下:
// 將hex表示方式轉換為rgb表示方式(這裡返回rgb陣列模式)
gradientColor.prototype.colorRgb = function(sColor) {
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
sColor = sColor.toLowerCase();
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = "#";
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
//處理六位的顏色值
let sColorChange = [];
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
}
return sColorChange;
} else {
return sColor;
}
};
// 將rgb表示方式轉換為hex表示方式
gradientColor.prototype.colorHex = function(rgb) {
let _this = rgb;
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
if (/^(rgb|RGB)/.test(_this)) {
let aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",");
let strHex = "#";
for (let i = 0; i < aColor.length; i++) {
let hex = Number(aColor[i]).toString(16);
hex = hex < 10 ? 0 + '' + hex : hex; // 保證每個rgb的值為2位
if (hex === "0") {
hex += hex;
}
strHex += hex;
}
if (strHex.length !== 7) {
strHex = _this;
}
return strHex;
} else if (reg.test(_this)) {
let aNum = _this.replace(/#/, "").split("");
if (aNum.length === 6) {
return _this;
} else if (aNum.length === 3) {
let numHex = "#";
for (let i = 0; i < aNum.length; i += 1) {
numHex += (aNum[i] + aNum[i]);
}
return numHex;
}
} else {
return _this;
}
};
複製程式碼
更多顏色轉換方法可以參考張鑫旭大大的文章www.zhangxinxu.com/wordpress/2…
然後我們可以直接這樣呼叫:
let color_list = new gradientColor("#706caa", "#f2f2b0", 360);
console.log(color_list);
複製程式碼
這樣控制檯我們就能看到我們計算出的漸變顏色陣列了
繪製圓
心急的小夥伴可能想畫圓還不簡單分分鐘畫一個圓
context.beginPath();
context.arc(150, 75, 50, 0, Math.PI * 2);
context.stroke();
複製程式碼
但是如果這麼畫圓,怎麼填充漸變色呢,想想前面的餅圖,我們把餅圖分成更多份,分成360份呢?是不是就相當於有很多線段,起始點一樣,長度一樣,圍繞起始點排列成一個圓!看到這裡聰明的你應該就想到該怎麼做了吧。是的,用畫線的方式,來畫圓,可能你覺得不可思議,moveTo和lineTo怎麼可能畫圓呢?下面我們就來分析如何畫一個圓。
大家還記得圓的極座標方程嗎,我給大家回顧回顧;
圓的極座標公式:ρ²=x²+y²,x=ρcosθ,y=ρsinθ tanθ=y/x,(x不為0)
下面的動圖顯示的很詳細,圓上任意一點與圓心的線段都是可以通過極座標表示出來的,並且如果我們每畫一根線都儲存下面,畫滿一圈後不就是一個填充圓嗎。
通過上面的分析,我們來寫程式碼
var center = [200, 200]; //圓的中心
var r = 100; // 圓的半徑
ctx.moveTo(center[0] + r, center[1]); //先把起始點移到圓上
for (var i = 0; i < 360; i++) {
var ii = i * Math.PI / 180; //角度轉弧度
ctx.lineWidth = 2;
var x = r + r * Math.cos(ii); //圓上任一點的橫座標
var y = r - r * Math.sin(ii); //圓上任一點縱座標
ctx.lineTo(x, y);
}
ctx.stroke();
複製程式碼
這樣我們就能看到最後的結果了;
完美,我們通過moveTo和lineTo畫出了一個圓,細心的小夥伴應該看到,右邊有一點缺失,沒連上,那是應為我們把圓分為360份但是,最後一份應該與第一份相連,也就是closePath,因此我們迴圈多加一次,361次就可以閉合了;
我們現在知道畫圓了,那麼同理我們把起始點移動到圓形,並且把圓心和圓上每一份的點連起來,不就是一個實心圓了嗎。
var center = [200, 200]; //圓的中心
var r = 100; // 圓的半徑
for (var i = 0; i < 360; i++) {
var ii = i * Math.PI / 180; //角度轉弧度
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(center[0], center[1]); //移動路徑到圓心
var x = r + r * Math.cos(ii); //圓上任一點的橫座標
var y = r - r * Math.sin(ii); //圓上任一點縱座標
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
複製程式碼
這樣我們就得到一個實心圓,一個由360根線組成的圓
那麼對應的,我們把每根線的顏色也由前面我們計算的漸變色來對應上,程式碼也很簡單;
var center = [200, 200]; //圓的中心
var r = 100; // 圓的半徑
for (var i = 0; i < 360; i++) {
var ii = i * Math.PI / 180; //角度轉弧度
ctx.save();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = color_list[i];
ctx.moveTo(center[0], center[1]); //移動路徑到圓心
var x = r + r * Math.cos(ii); //圓上任一點的橫座標
var y = r - r * Math.sin(ii); //圓上任一點縱座標
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
複製程式碼
完整程式碼請看這裡canvas圓錐漸變
很完美,一切都按我們設想的一樣。如果讀者有更好的方法,可以給我留言,一起學習交流交流。
本著只是做個題目,但是發現很有意思,後續我會封裝一下,做成一個漸變庫,支援各種漸變。
參考資料
- css角度漸變conic-gradient:www.cnblogs.com/coco1s/p/70…
- www.zhangxinxu.com/wordpress/2…
- developer.mozilla.org/zh-CN/docs/…