如何用 Node.JS 和 Canvas 自动生成图片

在本指南中,我将展示如何用 Node.JS 生成文章缩略图。以下是我用这种方法生成的一张图片:

null

本文的完整代码可以在 Git Gist 中找到。

由于 Node.JS ,它本身并不具备 canvas 功能。我们使用一个名为 canvas 的组件,将其导入到我们的 Node.JS 项目中。可以通过运行 npm i canvas 来安装它。

如何在 Canvas 中使用 Emoji

对于我生成的图片,我还想使用 Emoji。因此,我使用了该包的一个分支,名为 @napi-rs/canvas,它支持 Emoji。我使用的版本是 0.1.14,所以如果你在本指南操作时遇到问题,尝试用 npm i @napi-rs/[email protected] 命令安装它。

现在我们已经了解了基础知识,让我们开始吧。首先,让我们导入所有需要的包。在这里我导入了几个东西:

  • canvas --- 这是我们创建图片的方式。
  • fs --- 将图片写入服务器并保存。
  • cwebp --- 这是我们将图片保存为 webp 文件的方式,这样它就能针对网络进行优化。
  • fonts --- 导入 3 种字体------其中两种是 Inter 的不同版本,这是一种很好的字体,最后一种是 Apple Emoji 字体。你可以在 Inter 字体页面 找到 Inter 字体,在 Apple Emoji 字体页面 找到 Apple Emoji 字体。
js 复制代码
import canvas from '@napi-rs/canvas' // 用于创建画布。
import fs from 'fs' // 用于为我们的图片创建文件。
import cwebp from 'cwebp' // 用于将图片转换为 webp 格式。

// 加载我们需要的字体
GlobalFonts.registerFromPath('./fonts/Inter-ExtraBold.ttf', 'InterBold');
GlobalFonts.registerFromPath('./fonts/Inter-Medium.ttf','InterMedium');
GlobalFonts.registerFromPath('./fonts/Apple-Emoji.ttf', 'AppleEmoji');

如何用 JavaScript 生成文章缩略图

当我们在 HTML 画布上书写文本时,它通常不会自动换行。相反,我们需要创建一个函数来测量容器的宽度,并决定是否换行。注释后的函数如下所示

js 复制代码
// 这个函数接受 6 个参数:
// - ctx: 画布的上下文
// - text: 我们想要换行的文本
// - x: 文本的起始 x 坐标
// - y: 文本的起始 y 坐标
// - maxWidth: 最大宽度,即容器的宽度
// - lineHeight: 每行的高度(由我们定义)
const wrapText = function(ctx, text, x, y, maxWidth, lineHeight) {
    // 首先,按空格分割单词
    let words = text.split(' ');
    // 然后我们创建几个变量来存储行的信息
    let line = '';
    let testLine = '';
    // wordArray 是我们将要返回的数组,它将保存
    // 行文本的信息,以及它的 x 和 y 起始位置
    let wordArray = [];
    // totalLineHeight 将保存行高的信息
    let totalLineHeight = 0;

    // 接下来,我们遍历每个单词
    for(var n = 0; n < words.length; n++) {
        // 测试它的长度
        testLine += `${words[n]} `;
        var metrics = ctx.measureText(testLine);
        var testWidth = metrics.width;
        // 如果太长,则我们开始新的一行
        if (testWidth > maxWidth && n > 0) {
            wordArray.push([line, x, y]);
            y += lineHeight;
            totalLineHeight += lineHeight;
            line = `${words[n]} `;
            testLine = `${words[n]} `;
        }
        else {
            // 否则我们只有一行!
            line += `${words[n]} `;
        }
        // 当所有单词完成后,我们将剩余的内容推入数组
        if(n === words.length - 1) {
            wordArray.push([line, x, y]);
        }
    }

    // 返回包含单词的数组,以及总行高
    // 总行高将是 (总行数 - 1) * 行高
    return [ wordArray, totalLineHeight ];
}

现在我们开始编写 generateMainImage 函数。这个函数将接受我们提供的所有信息,并为你的文章或网站生成一张图片。

在这个函数中,你可以传入任何你想要的颜色,选择权在你手中。

js 复制代码
// 这个函数接受 5 个参数:
// canonicalName: 这是我们用来保存图片的名字
// gradientColors: 一个包含两种颜色的数组,例如 [ '#ffffff', '#000000' ],用于我们的渐变
// articleName: 你希望在图片中显示的文章或网站的标题
// articleCategory: 该文章所属的类别------或者文章的副标题
// emoji: 你希望在图片中显示的 emoji
const generateMainImage = async function(canonicalName, gradientColors, articleName, articleCategory, emoji) {

    articleCategory = articleCategory.toUpperCase();
    // gradientColors 是一个数组 [ c1, c2 ]
    if(typeof gradientColors === "undefined") {
        gradientColors = [ "#8005fc", "#073bae"]; // 备用值
    }

    // 创建画布
    const canvas = createCanvas(1342, 853);
    const ctx = canvas.getContext('2d')

    // 添加渐变------我们使用 createLinearGradient 来实现这一点
    let grd = ctx.createLinearGradient(0, 853, 1352, 0);
    grd.addColorStop(0, gradientColors[0]);
    grd.addColorStop(1, gradientColors[1]);
    ctx.fillStyle = grd;
    // 填充我们的渐变
    ctx.fillRect(0, 0, 1342, 853);

    // 在画布上书写我们的 Emoji
    ctx.fillStyle = 'white';
    ctx.font = '95px AppleEmoji';
    ctx.fillText(emoji, 85, 700);

    // 添加我们的标题文本
    ctx.font = '95px InterBold';
    ctx.fillStyle = 'white';
    let wrappedText = wrapText(ctx, articleName, 85, 753, 1200, 100);
    wrappedText[0].forEach(function(item) {
        // 我们将填充数组中的文本 item[0],在坐标 [x, y]
        // x 是数组中的 item[1]
        // y 是数组中的 item[2],减去行高(wrappedText[1]),再减去 emoji 的高度(200px)
        ctx.fillText(item[0], item[1], item[2] - wrappedText[1] - 200); // 200 是 emoji 的高度
    })

    // 将我们的类别文本添加到画布上
    ctx.font = '50px InterMedium';
    ctx.fillStyle = 'rgba(255,255,255,0.8)';
    ctx.fillText(articleCategory, 85, 553 - wrappedText[1] - 100); // 853 - 200 用于 emoji,-100 用于 1 行的行高

    if(fs.existsSync(`./views/images/intro-images/${canonicalName}.png`)) {
        return '图片已存在!我们没有创建任何图片'
    }
    else {
        // 将画布设置为 png 格式
        try {
            const canvasData = await canvas.encode('png');
            // 保存文件
            fs.writeFileSync(`./views/images/intro-images/${canonicalName}.png`, canvasData);
        }
        catch(e) {
            console.log(e);
            return '这次无法创建 png 图片。'
        }
        try {
            const encoder = new cwebp.CWebp(path.join(__dirname, '../', `/views/images/intro-images/${canonicalName}.png`));
            encoder.quality(30);
            await encoder.write(`./views/images/intro-images/${canonicalName}.webp`, function(err) {
                if(err) console.log(err);
            });
        }
        catch(e) {
            console.log(e);
            return '这次无法创建 webp 图片。'
        }

        return '图片已成功创建!';
    }
}

用 Node.JS 生成文章图片

让我们仔细观察一下这个函数,以便完全理解其中的原理。我们首先准备数据------将类别转换为大写,并设置一个默认渐变。然后我们创建画布,并使用 getContext 初始化一个绘制的空间。

js 复制代码
articleCategory = articleCategory.toUpperCase();
// gradientColors 是一个数组 [ c1, c2 ]
if(typeof gradientColors === "undefined") {
    gradientColors = [ "#8005fc", "#073bae"]; // 备用值
}

// 创建画布
const canvas = createCanvas(1342, 853);
const ctx = canvas.getContext('2d')

然后绘制渐变:

js 复制代码
// 添加渐变------我们使用 createLinearGradient 来实现这一点
let grd = ctx.createLinearGradient(0, 853, 1352, 0);
grd.addColorStop(0, gradientColors[0]);
grd.addColorStop(1, gradientColors[1]);
ctx.fillStyle = grd;
// 填充我们的渐变
ctx.fillRect(0, 0, 1342, 853);

图片上绘制 emoji 文本。

js 复制代码
// 在画布上书写我们的 Emoji
ctx.fillStyle = 'white';
ctx.font = '95px AppleEmoji';
ctx.fillText(emoji, 85, 700);

现在我们使用我们的换行函数 wrapText。我们将传入相当长的 articleName,并从图片底部附近的 85, 753 开始。由于 wrapText 返回一个数组,我们将遍历该数组以确定每行的坐标,并将它们绘制到画布上:

js 复制代码
    // 添加我们的标题文本
    ctx.font = '95px InterBold';
    ctx.fillStyle = 'white';
    let wrappedText = wrapText(ctx, articleName, 85, 753, 1200, 100);
    wrappedText[0].forEach(function(item) {
        // 我们将填充数组中的文本 item[0],在坐标 [x, y]
        // x 是数组中的 item[1]
        // y 是数组中的 item[2],减去行高(wrappedText[1]),再减去 emoji 的高度(200px)
        ctx.fillText(item[0], item[1], item[2] - wrappedText[1] - 200); // 200 是 emoji 的高度
    })

    // 将我们的类别文本添加到画布上
    ctx.font = '50px InterMedium';
    ctx.fillStyle = 'rgba(255,255,255,0.8)';
    ctx.fillText(articleCategory, 85, 553 - wrappedText[1] - 100); // 853 - 200 用于 emoji,-100 用于 1 行的行高

将画布图片保存到服务器

好了,现在我们已经创建了图片,让我们将它保存到服务器上:

  • 首先,我们将检查文件是否存在。如果存在,我们将返回图片。
  • 如果文件不存在,我们将尝试使用 canvas.encode 创建 png 版本,并使用 fs.writeFileSync 保存它。
  • 如果一切顺利,我们将使用 cwebp 保存一个.webp 版本的文件,这比 .png 版本小得多。
js 复制代码
  if(fs.existsSync(`./views/images/intro-images/${canonicalName}.png`)) {
      return '图片已存在!我们没有创建任何图片'
  }
  else {
      // 将画布设置为 png 格式
      try {
          const canvasData = await canvas.encode('png');
          // 保存文件
          fs.writeFileSync(`./views/images/intro-images/${canonicalName}.png`, canvasData);
      }
      catch(e) {
          console.log(e);
          return '这次无法创建 png 图片。'
      }
      try {
          const encoder = new cwebp.CWebp(path.join(__dirname, '../', `/views/images/intro-images/${canonicalName}.png`));
          encoder.quality(30);
          await encoder.write(`./views/images/intro-images/${canonicalName}.webp`, function(err) {
              if(err) console.log(err);
          });
      }
      catch(e) {
          console.log(e);
          return '这次无法创建 webp 图片。'
      }

      return '图片已成功创建!';
  }

要运行这个文件:

复制代码
node index.js

以下是通过这种方式生成的一张图片的示例:

null

原文:fjolt.com/article/jav...

相关推荐
花楸树7 分钟前
前端搭建 MCP Client(Web版)+ Server + Agent 实践
前端·人工智能
wuaro8 分钟前
RBAC权限控制具体实现
前端·javascript·vue
专业抄代码选手12 分钟前
【JS】instanceof 和 typeof 的使用
前端·javascript·面试
用户00798136209713 分钟前
6000 字+6 个案例:写给普通人的 MCP 入门指南
前端
用户876128290737417 分钟前
前端ai对话框架semi-design-vue
前端·人工智能
干就完了120 分钟前
项目中遇到浏览器跨域前端和后端解决方案以及大概过程
前端
我是福福大王22 分钟前
前后端SM2加密交互问题解析与解决方案
前端·后端
实习生小黄26 分钟前
echarts 实现环形渐变
前端·echarts
_未知_开摆33 分钟前
uniapp APP端在线升级(简版)
开发语言·前端·javascript·vue.js·uni-app
sen_shan1 小时前
Vue3+Vite+TypeScript+Element Plus开发-02.Element Plus安装与配置
前端·javascript·typescript·vue3·element·element plus