前言
昨天公司培训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/…