背景
某些情况下可能需要使用canvas实现线性渐变,需求是渐变色恰好填充整个画布,并且渐变的方向不是水平或垂直的,而是任意角度的,举个css3实现的栗子:
background: linear-gradient(60deg, red, blue);
如果用canvas实现上面的效果,首先要创建一个渐变对象:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.createLinearGradient(x0, y0, x1, y1);
x0,y0是渐变起始坐标,x1,y1是渐变结束坐标。
具体请参考API。
那么问题来了,如何确定坐标参数x0, y0, x1, y1?
实现
在确定坐标参数之前,先简单了解下css的实现原理,直接放图:
默认情况下,渐变会平滑地从一种颜色过渡到另一种颜色,也就是说渐变线会通过元素的中点,上面说的渐变色恰好填充整个画布也是这个意思,那么以元素的中点为原点建立坐标系,那么可以得到下面这张图:
其中矩形是元素,M是渐变线,从左下至右上方向,Q、W分别是过矩形的两个对角点,并与M垂直的两条线,颜色渐变的范围就在这两条线之间,(x0’,y0’),(x1’,y1’)分别是两个垂点的坐标,暂且称为I点,J点,而我们要求的(x0,y0),(x1,y1)其实就是将坐标系转换成canvas坐标系(左上角为原点,向下为Y轴正方向)后I点和J点的坐标,有了矩形的宽高和渐变线的角度值,根据简单的几何知识就能算出两点的坐标,不过需要注意的是,渐变线与矩形的交点可能落在水平线上,也可能落在垂直线上,所以需要分两块逻辑进行计算,实现代码如下:
/**
* @description: 计算canvas渐变起始坐标
* @param {number} canvas width
* @param {number} canvas height
* @param {number} angle 角度
* @return {*}
*/
function calculateGradientCoordinate(
width,
height,
angle = 180,
) {
if (angle >= 360) angle = angle - 360;
if (angle < 0) angle = angle + 360;
angle = Math.round(angle);
// 当渐变轴垂直于矩形水平边上的两种结果
if (angle === 0) {
return {
x0: Math.round(width / 2),
y0: height,
x1: Math.round(width / 2),
y1: 0,
};
}
if (angle === 180) {
return {
x0: Math.round(width / 2),
y0: 0,
x1: Math.round(width / 2),
y1: height,
};
}
// 当渐变轴垂直于矩形垂直边上的两种结果
if (angle === 90) {
return {
x0: 0,
y0: Math.round(height / 2),
x1: width,
y1: Math.round(height / 2),
};
}
if (angle === 270) {
return {
x0: width,
y0: Math.round(height / 2),
x1: 0,
y1: Math.round(height / 2),
};
}
// 从矩形左下角至右上角的对角线的角度
const alpha = Math.round(
(Math.asin(width / Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))) *
180) /
Math.PI,
);
// 当渐变轴分别于矩形的两条对角线重合情况下的四种结果
if (angle === alpha) {
return {
x0: 0,
y0: height,
x1: width,
y1: 0,
};
}
if (angle === 180 - alpha) {
return {
x0: 0,
y0: 0,
x1: width,
y1: height,
};
}
if (angle === 180 + alpha) {
return {
x0: width,
y0: 0,
x1: 0,
y1: height,
};
}
if (angle === 360 - alpha) {
return {
x0: width,
y0: height,
x1: 0,
y1: 0,
};
}
// 以矩形的中点为坐标原点,向上为Y轴正方向,向右为X轴正方向建立直角坐标系
let x0 = 0,
y0 = 0,
x1 = 0,
y1 = 0;
// 当渐变轴与矩形的交点落在水平线上
if (
angle < alpha || // 处于第一象限
(angle > 180 - alpha && angle < 180) || // 处于第二象限
(angle > 180 && angle < 180 + alpha) || // 处于第三象限
angle > 360 - alpha // 处于第四象限
) {
// 将角度乘以(PI/180)即可转换为弧度
const radian = (angle * Math.PI) / 180;
// 当在第一或第四象限,y是height / 2,否则y是-height / 2
const y = angle < alpha || angle > 360 - alpha ? height / 2 : -height / 2;
const x = Math.tan(radian) * y;
// 当在第一或第二象限,l是width / 2 - x,否则l是-width / 2 - x
const l =
angle < alpha || (angle > 180 - alpha && angle < 180)
? width / 2 - x
: -width / 2 - x;
const n = Math.pow(Math.sin(radian), 2) * l;
x1 = x + n;
y1 = y + n / Math.tan(radian);
x0 = -x1;
y0 = -y1;
}
// 当渐变轴与矩形的交点落在垂直线上
if (
(angle > alpha && angle < 90) || // 处于第一象限
(angle > 90 && angle < 180 - alpha) || // 处于第二象限
(angle > 180 + alpha && angle < 270) || // 处于第三象限
(angle > 270 && angle < 360 - alpha) // 处于第四象限
) {
// 将角度乘以(PI/180)即可转换为弧度
const radian = ((90 - angle) * Math.PI) / 180;
// 当在第一或第二象限,x是width / 2,否则x是-width / 2
const x =
(angle > alpha && angle < 90) || (angle > 90 && angle < 180 - alpha)
? width / 2
: -width / 2;
const y = Math.tan(radian) * x;
// 当在第一或第四象限,l是height / 2 - y,否则l是-height / 2 - y
const l =
(angle > alpha && angle < 90) || (angle > 270 && angle < 360 - alpha)
? height / 2 - y
: -height / 2 - y;
const n = Math.pow(Math.sin(radian), 2) * l;
x1 = x + n / Math.tan(radian);
y1 = y + n;
x0 = -x1;
y0 = -y1;
}
// 坐标系更改为canvas标准,Y轴向下为正方向
x0 = Math.round(x0 + width / 2);
y0 = Math.round(height / 2 - y0);
x1 = Math.round(x1 + width / 2);
y1 = Math.round(height / 2 - y1);
return { x0, y0, x1, y1 };
}