修身养性,知行合一

  • 首页
  • 爱码
    • 系统
    • 数据库
    • JavaScript
    • CSharp
    • Python
  • 生活
    • 文化
    • 美食
  • 杂谈
  • 关于
修身养性,知行合一
码字,杂谈
  1. 首页
  2. 爱码
  3. 前端
  4. JavaScript
  5. 正文

绘制一个可重用的线条阴影

2025年6月17日 43点热度 0人点赞 0条评论

在绘制线条阴影的时候,一般来说有两种方案:

  • 绘制完整的一张图片
  • 绘制一个片段,然后重复渲染

我们今天讨论第二种。它更加高效,更适合大范围渲染。

考虑功能

  1. 看上去是一个整体,所以拼接时所有线条要可以完全连上
  2. 可以调整线条角度
  3. 可以调整线条间距
  4. 可以调整线条粗细和颜色

渲染思路

我们可以将线条分为三种状态:

  • 横线
  • 竖线
  • 斜线

前两种,特殊处理即可,我们需要关注最后一种 斜线,我们只需要关注斜线的角度即可。从根本上说,横线 与 竖线 也是斜线的特殊角度而已,所以我们可以统一计算,遇到 0 || 90 的时候,将它们处理成对应的横/竖即可。

为了绘制斜线,我们这样思考:我们让斜线作为当前图形的对角线,这样无论怎么拼接,这条线都是连续的。

file

如上图,这样渲染出来的整体,就是一条连续的线。然后,我们只需要在这条线的两侧再分别渲染一定间距之外的平行线,就形成了阴影线条。

那么,间距线条如何绘制?可以考虑一个取巧的简单方案:将间距设置为斜线与两个对角的距离:

file

如图:红线部分就是间距,而黄色虚线则是相邻的另一条连线的位置。

根据这种方案,我们可以轻松滑出重复无限的阴影效果。

考虑完整情况

我们需要两个参数:

  • 斜线的角度
  • 斜线之间的间距

有了这两个参数,我们就可以绘制出需要的斜线。但是需要考虑角度会影响斜线的出发点和方向。

综合以上的逻辑,我们把斜线统一从左上角出发,它将有几种情况(以角度 0 为从左至右的横线为原点):

  • 0度:横线,此时在矩形顶部画一条线即可
  • 1-89度:斜线,左上角到右下角画一条线
  • 90度:竖线,此时在矩形左侧画一条线即可
  • 91-179度:斜线,此时需要从右上角到左下角画一条
  • 180度:横线,同 0度

我们只考虑 180度 之内,超过了我们使用 % 将剩余角度取余,展示效果一样。

在以上的情况中,我们需要画四条线。原因有二:

  • 为了统一实现方法
  • 让连线看上去更加连贯(是的,在两个矩形对角相连的部分,连线是有一定分割的,连线如果过粗,它只会在当前矩形中展示,并不会溢出,所以连接处的垂直方向上,会出现空缺。如下图)

file

所以我们需要画四条线,它们出发的四个点分别是:

file

如图所示,四个点出发的线,无论是向左(绿色,相当于91-179度)还是向右(黄色,相当于 1-89度),它们都可以覆盖到展示区域。

具体实现

有了以上的思路,我们就可以实现它。

首先,获取参数:

  • 角度:angle
  • 间距:spacing
  • 线条宽度:width

将角度固定在我们需要的范围内:

const θ = angle % 180;
const rad = (θ * Math.PI) / 180;

有了角度,我们就可以通过间距,来获取我们应该创建多大的一个图片(图片的宽高就是斜线对角线的宽高):

let canvasWidth: number;
let canvasHeight: number;
if (θ === 0) {
    canvasWidth = spacing;
    canvasHeight = width + spacing;
} else if (θ === 90) {
    canvasWidth = width + spacing;
    canvasHeight = spacing;
} else {
    canvasWidth = Math.abs(Math.ceil((width + spacing) / Math.sin(rad)));
    canvasHeight = Math.abs(Math.ceil((width + spacing) / Math.cos(rad)));
}

有了图片的宽高,就可以绘制线条:

if (θ === 0 || θ === 90) {
  if (θ === 0) {
    // 水平线
    ctx.moveTo(0, 0);
    ctx.lineTo(canvasWidth, 0);
  } else {
    // 垂直线
    ctx.moveTo(0, 0);
    ctx.lineTo(0, canvasHeight);
  }
} else {
  // 倾斜线条
  for (let x = -canvasWidth; x < canvasWidth * 2; x += canvasWidth) {
    // 计算线条的起点和终点
    const x1 = x;
    const y1 = 0;
    const x2 = x + (Math.cos(rad) < 0 ? -canvasWidth : canvasWidth);
    const y2 = canvasHeight;

    ctx.moveTo(Math.ceil(x1), Math.ceil(y1));
    ctx.lineTo(Math.ceil(x2), Math.ceil(y2));
  }
}

最后,转为图片并在完整的图片中重复它就可以了。

完整代码

function createStripePattern(options = {}) {
  const {
    color = "#c9c9c9",
    width = 1,
    angle = 30,
    spacing = 10
  } = options;

  // 将角度转换到 0-180 范围内
  const θ = angle % 180;
  const rad = (θ * Math.PI) / 180;

  let canvasWidth;
  let canvasHeight;

  if (θ === 0) {
    canvasWidth = spacing;
    canvasHeight = width + spacing;
  } else if (θ === 90) {
    canvasWidth = width + spacing;
    canvasHeight = spacing;
  } else {
    canvasWidth = Math.abs(Math.ceil((width + spacing) / Math.sin(rad)));
    canvasHeight = Math.abs(Math.ceil((width + spacing) / Math.cos(rad)));
  }

  // 创建临时 canvas
  const canvas = document.createElement('canvas');
  canvas.width = canvasWidth;
  canvas.height = canvasHeight;
  const ctx = canvas.getContext('2d');

  // 设置线条样式
  ctx.strokeStyle = color;
  ctx.lineWidth = width;
  ctx.lineCap = 'butt';
  ctx.lineJoin = 'miter';

  ctx.beginPath();

  // 绘制线条
  if (θ === 0 || θ === 90) {
    if (θ === 0) {
      // 水平线
      ctx.moveTo(0, 0);
      ctx.lineTo(canvasWidth, 0);
    } else {
      // 垂直线
      ctx.moveTo(0, 0);
      ctx.lineTo(0, canvasHeight);
    }
  } else {
    // 倾斜线条
    for (let x = -canvasWidth; x < canvasWidth * 2; x += canvasWidth) {
      // 计算线条的起点和终点
      const x1 = x;
      const y1 = 0;
      const x2 = x + (Math.cos(rad) < 0 ? -canvasWidth : canvasWidth);
      const y2 = canvasHeight;

      ctx.moveTo(Math.ceil(x1), Math.ceil(y1));
      ctx.lineTo(Math.ceil(x2), Math.ceil(y2));
    }
  }

  ctx.stroke();

  // 转换为图片并返回
  const img = new Image();
  img.src = canvas.toDataURL();

  return img;
}
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: JavaScript
最后更新:2025年6月17日

jeremyjone

这个人很懒,什么都没留下

打赏 点赞
< 上一篇

文章评论

取消回复

文章目录
  • 考虑功能
  • 渲染思路
  • 考虑完整情况
  • 具体实现
  • 完整代码
最新 热点 随机
最新 热点 随机
绘制一个可重用的线条阴影 node-sass 的安装 解决端口被占的问题 vue3 组件 Props 的声明方式 给 div 添加选中状态 请求的取消
绘制一个可重用的线条阴影
JavaScript 之 canvas(一) windows中自定义图标不能正常显示 将Windows Terminal添加到右键菜单 C#读取Excel的内容和图片及图片位置 解决mount cifs时出现"is not a valid block device"的问题 瀑布流的实现

(っ•̀ω•́)っ✎⁾⁾ 开心每一天

COPYRIGHT © 2021 jeremyjone.com. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

京ICP备19012859号-1

京公网安备 11010802028585号