canvas线性渐变实现:根据渐变线角度计算坐标x0,y0,x1,y1

1 篇文章 0 订阅

背景

某些情况下可能需要使用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 };
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值