使用 SVG 生成 blob 字符!

2025-06-08

使用 SVG 生成 blob 字符!

这世上我最爱的两件事:圆润的形状和在物体上画活动眼珠。本教程结合了我两大爱好,希望能在创作过程中,为大家带来生成艺术的入门教程。

以下是我们将要制作的内容:

这是一个相当简单的例子,展示了通过生成设计/艺术方法可以实现的目标,但希望您可以对其进行扩展。


先决条件ℹ️

无需任何生成艺术知识!本教程非常适合熟悉 JavaScript / HTML / CSS 并希望开始学习生成艺术的人士。


什么是生成艺术?🤔

我在泰特美术馆网站上找到了关于生成艺术的最简单的定义——

“生成艺术是使用预定系统创作的艺术,通常包含偶然因素——通常应用于基于计算机的艺术”

我认为这是完美的,并且在我们学习本教程的过程中值得牢记,特别是如果您对这些内容还不熟悉。


开始构建吧!🔨

在本教程中,我们将使用 SVG 来渲染我们的角色,使用 JavaScript 来决定渲染什么,并使用少量 CSS 来确保它们能够很好地显示在页面上。

我还添加了几个外部 JS 库,以使我们的代码保持简单和干净。

我已经创建了一个 CodePen,你可以在这里fork ,里面已经预先添加了所有这些东西。一旦你 fork 了 CodePen 或设置好了环境,我们就可以开始创建角色了!

包安装

如果您从头开始创建自己的环境而不是分叉 CodePen,则可以使用以下命令安装所需的软件包:

npm install @georgedoescode/spline @svgdotjs/svg.js
Enter fullscreen mode Exit fullscreen mode

然后可以将它们导入到你的 JavaScript 中,如下所示:

import { spline } from "@georgedoescode/spline";
import { SVG } from "@svgdotjs/svg.js";
Enter fullscreen mode Exit fullscreen mode

注意:如果您计划设置自己的环境,请记住您可能需要 Parcel 或 Webpack 等捆绑器来处理这些类型的模块导入。


一块空白的画布🖼️

如果您通过分叉上述 CodePen 来启动您的项目,那么您已经设置了此 CSS。

如果没有,请随意将以下内容添加到您的项目中,以便将其<svg />完美地放置在视口的中心。或者不添加!用艾斯利兄弟的话来说——这是你的事,做你想做的事。

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  display: grid;
  place-items: center;
}

svg {
  width: 75vmin;
  height: 75vmin;
}
Enter fullscreen mode Exit fullscreen mode

Blob 的诞生👶

这里不会有鸟、蜜蜂、鹳之类的。只有你选择的代码编辑器和一些 ES6 类语法 🤖

首先,我们需要定义一些属性:

  • width: SVG 的 viewBox 宽度
  • height: SVG 的 viewBox 高度
  • 目标:<svg />元素应添加到 DOM 中的位置
  • svg:svg.js我们将用于渲染的实例
  • x:角色在 SVG viewBox 中的水平位置
  • y:角色在 SVG viewBox 中的垂直位置

考虑到所有这些,我们应该有一个如下所示的类构造函数:

class BlobCharacter {
  constructor(width, height, target) {
    // viewBox width & height dimensions
    this.width = width;
    this.height = height;

    // position of our character within the viewBox (the center)
    this.x = this.width / 2;
    this.y = this.height / 2;

    // <svg /> element (svg.js instance) we are using to render
    this.svg = SVG()
      .addTo(target) // mount instance to our target
      .viewbox(0, 0, this.width, this.height); // set the <svg /> viewBox attribute
  }
}
Enter fullscreen mode Exit fullscreen mode

一旦我们定义了类构造函数,我们就可以通过调用以下命令创建一个全新的 Blob 角色:

// viewBox w x h, target to append the <svg /> element to
const character = new BlobCharacter(200, 200, document.body);
Enter fullscreen mode Exit fullscreen mode

您现在还看不到任何东西,这很酷。

注意:viewBox属性

如果您是 SVG 新手,并且想知道 viewBox 是什么,它本质上定义了一个坐标空间,您可以根据该坐标空间绘制任何内容。在我们的例子中,该坐标空间是 200 x 200px。

定义 viewBox 后,SVG 将根据您定义的空间绘制所有内容,并缩放至任意分辨率。这使得创建响应式生成作品变得轻松便捷!

如果你想了解更多关于 viewBox 的内容,这里有一篇关于 CSS 技巧的精彩文章


所有生物,无论大小🐭🐘

现在我们已经为我们的角色完成了所有的“样板”设置,是时候享受一些乐趣了!

我们首先需要考虑的是角色的整体尺寸。它们应该大还是小?我们该如何定义呢?

由于我们的角色形状将基于一个圆(稍后会详细介绍),因此我们可以用圆的半径来定义角色的大小。别担心,我不是数学家,这里不会讲得太深奥。

考虑到我们的 viewBox 尺寸是 200x200,50 到 80 之间的数字应该很合适。这里有一个非常有用的函数,我们可以用它来生成一个范围内的随机数:

// choose a number within a range, integer (whole number) by default
function random(min, max, float = false) {
  const val = Math.random() * (max - min) + min;

  if (float) {
    return val;
  }

  return Math.floor(val);
}
Enter fullscreen mode Exit fullscreen mode

使用这个非常方便的实用函数,我们可以为我们的角色定义一个随机大小,如下所示:

class BlobCharacter {
  constructor(width, height, target) {
    ...
    // choose a random size / radius for our character
    this.size = random(50, 80);
  }
}
Enter fullscreen mode Exit fullscreen mode

完美!我们仍然看不到任何东西,但代码看起来不错。


画身体✏️

我认为下一步应该画出角色的身体。这可能是本教程中最棘手的部分,但如果有点困惑,也不用担心!

慢慢来,尝试各种代码,将其分解,再重新组合。

我发现在生成艺术中,我经常会用一段代码一段时间才能真正理解它。我觉得这没关系,没人会因为你精美的作品而看出你对数学知识一窍不通。如果它看起来很酷,那就很酷。我们是在创作艺术,不是在发表研究论文!

无论如何,进入代码...

为了绘制角色的身体,我们将执行以下操作:

  1. 绘制圆周上的等距点
  2. {x, y}为每个点的值添加一点随机性
  3. 画一条经过所有点的平滑曲线

可以将此代码添加到drawBody()我们的 BlobCharacter 类的函数中(所有代码都带有注释,以概述其上下文功能)

...
drawBody() {
  // choose a random number of points
  const numPoints = random(3, 12);
  // step used to place each point at equal distances
  const angleStep = (Math.PI * 2) / numPoints;

  // keep track of our points
  const points = [];

  for (let i = 1; i <= numPoints; i++) {
    // how much randomness should be added to each point
    const pull = random(0.75, 1, true);

    // x & y coordinates of the current point
    const x = this.x + Math.cos(i * angleStep) * (this.size * pull);
    const y = this.y + Math.sin(i * angleStep) * (this.size * pull);

    // push the point to the points array
    points.push({ x, y });
  }

  // generate a smooth continuous curve based on the points, using bezier curves. spline() will return an svg path-data string. The arguments are (points, tension, close). Play with tension and check out the effect!
  const pathData = spline(points, 1, true);

  // render the body in the form of an svg <path /> element!
  this.svg
    .path(pathData)
    .stroke({
      width: 2,
      color: '#000'
    })
    .fill('transparent');
}
Enter fullscreen mode Exit fullscreen mode

一旦添加到类中,就可以像这样调用它:

character.drawBody();
Enter fullscreen mode Exit fullscreen mode

好!鼓声响起……

如果你查看浏览器/CodePen 窗口,你现在应该会看到一个很棒的随机 blob 形状。刷新浏览器或重新运行代码,每次都应该能看到一个新的形状!

有机斑点形状

我们正在创作生成艺术!

注意:spline()函数

您在此处看到的函数spline()是另一个非常有用的实用程序。它只是通过一组点绘制一条平滑的曲线{ x, y }。它创建的形状应该始终完美“闭合”,从而获得非常令人满意、自然的最终结果。它的专业名称是Catmull-Rom 样条线

你可以在这里找到我创建的版本的源代码。感谢https://twitter.com/cassiecodes让我领略了样条曲线的魅力 🙌


画眼睛👀

好了,我们终于找到了一个很棒的有机斑点形状。我们差不多可以就此打住。你在网络上随处可见这些斑点,它们可以成为一种用途极其广泛的设计素材。快速搜索一下 Dribbble,就能找到一些例子!

不过我们应该加一些活动眼珠。有了活动眼珠,一切都看起来更好了。

让我们drawEye()向 BlobCharacter 类添加一个函数:

// x position, y position, radius / size
drawEye(x, y, size) {
  // create a new svg <group /> to add all the eye content to
  const eye = this.svg.group();
  // <group /> elements do not have an x and y attribute, so we need to "transform" it to the right position
  eye.transform({ translateX: x, translateY: y });

  // add the outer ring of the eye (an svg <circle /> element) to our eye <group />
  eye
    .circle(size)
    // cx / cy are the { x, y } values for the svg <circle /> element
    .cx(0)
    .cy(0)
    .stroke({
      width: 2,
      color: '#000'
    })
    .fill('#fff');

  // add the inner part of the eye (another svg <circle /> element) to our eye <group />
  eye
    .circle(size / 2)
    .cx(0)
    .cy(0)
    .fill('#000')
}
Enter fullscreen mode Exit fullscreen mode

现在这还不能做太多事情。我们来添加另一个drawEyes()函数,它会传入drawEye()一些值进行调用。

drawEyes() {
  // ensure the width of two eyes never exceeds 50% of the characters body size
  const maxWidth = this.size / 2;
  // if a random number between 0 and 1 is greater than 0.75, the character is a cyclops!
  const isCyclops = random(0, 1, true) > 0.75;
  // the size of each (or only) eye.
  const eyeSize = random(maxWidth / 2, maxWidth);

  if (isCyclops) {
    // draw just 1 eye, in the centre of the character
    this.drawEye(this.x, this.y, eyeSize);
  } else {
    // draw 2 eyes, equidistant from the centre of the character
    this.drawEye(this.x - maxWidth / 2, this.y, eyeSize);
    this.drawEye(this.x + maxWidth / 2, this.y, eyeSize);
  }
}
Enter fullscreen mode Exit fullscreen mode

然后我们可以drawEyes()用同样的方式调用drawBody()

character.drawEyes()
Enter fullscreen mode Exit fullscreen mode

现在看看浏览器,应该能看到之前那个很棒的泡泡体,不过还加了几个新的活动眼珠(或者一只)。真棒!

添加了活动眼珠的有机斑点形状

现在是检查 DOM 的好时机,仔细查看<svg />包含 blob 字符所有部分的元素。你应该看到类似这样的内容:

SVG DOM 节点的屏幕截图

这是使用 SVG 进行生成艺术的一大优点。由于你有一个可视化的 DOM 树可供探索,因此调试/可视化变得非常容易。

检查元素应该能突出显示一直以来为我们做的<svg />事情。它只是简化了 SVG DOM 元素的动态创建/更新。如果没有库的话,这可能会非常冗长。svg.js


是时候拿出蜡笔了🖍️

我们的角色看起来棒极了。它个性十足,但我觉得加点颜色会很酷。不过,如果你喜欢,也可以保留黑白。这样就有一种酷酷的卡哇伊素描风格。

这里引入一些颜色的简单方法是定义一个primaryColor、一个lightColor要替换的#fff和一个darkColor要替换的#000

darkColorlightColor值都用 进行了着色baseColor。我经常这样做,觉得这是一个让调色板看起来连贯的好技巧。在 UI 环境中,它也非常有效。

让我们在一个新函数中设置颜色值setColors()

  setColors() {
    // random hue 
    const hue = random(0, 360);
    // random saturation, keeping it quite high here as a stylistic preference
    const saturation = random(75, 100);
    // random lightness, keeping it quite high here as a stylistic preference
    const lightness = random(75, 95);

    // base color
    this.primaryColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    // almost black, slightly tinted with the base color
    this.darkColor = `hsl(${hue}, ${saturation}%, 2%)`;
    // almost white, slightly tinted with the base color
    this.lightColor = `hsl(${hue}, ${saturation}%, 98%)`;
  }
Enter fullscreen mode Exit fullscreen mode

我总是使用 HSL 来表示颜色,因为我发现它直观易用,并且在生成环境中易于修改。如上面的代码片段所示,我选择了随机的 H/S/L 值,并使用 JavaScript 模板字符串将它们组合起来。

然后我们可以调用setColors()BlobCharacter 构造函数:

class BlobCharacter {
  constructor(width, height, target) {
    ...
    this.setColors();
  }
}
Enter fullscreen mode Exit fullscreen mode

一旦定义了颜色,我们就可以在整个代码中应用它们:

  • this.primaryColor代替transparent身体填充
  • this.darkColor对于所有出现的#000
  • this.lightColor对于所有出现的#fff

最后,我们可以设置基本<svg />背景颜色来为this.lightColor我们的角色创建一个丰富多彩的背景:

class BlobCharacter {
  constructor(width, height, target) {
    ...
    this.setColors();

    this.svg.node.style.background = this.lightColor;
  }
}
Enter fullscreen mode Exit fullscreen mode

你的角色现在看起来应该类似于下图。记住,颜色和形状每次都会有所不同!

色彩斑斓的斑点人物


多样性是生活的调味剂🌶️

我们的角色设计完成了!不过还有最后一件事可以补充……

目前我们只看到一个角色示例。如果能更详细地展示一下这件作品的生成性就更好了。我们可以通过定期重新生成和渲染角色来实现。

为了做到这一点,我们将所有绘图功能包装到类中draw()的一个方法中BlobCharacter

draw() {
  // clear the <svg /> element
  this.svg.clear();
  // generate new colors
  this.setColors();
  // set the svg background color
  this.svg.node.style.background = this.lightColor;
  // generate a new body shape and render it
  this.drawBody();
  // genearte new eye(s) and render them
  this.drawEyes();
}
Enter fullscreen mode Exit fullscreen mode

然后,我们可以在创建实例后直接调用此方法drawBody()而不是运行。然后,我们可以每 1.5 秒调用一次此方法来创建新角色:drawEyes()character

// viewBox w x h, target to append the <svg /> element to
const character = new BlobCharacter(200, 200, document.body);

// draw the initial character
character.draw();

setInterval(() => {
  // every 1.5s after the initial render, draw a new character
  character.draw();
}, 1500);
Enter fullscreen mode Exit fullscreen mode

希望您现在能看到类似这样的内容...

展示不同 blob 字符生成的 gif


就这些啦,朋友们!👋

哇,我们成功了!希望你完成了本教程,并创建了一个很酷的角色,同时学习了一些关于生成艺术的知识。

如果您遇到任何困难,请查看最终的示例代码作为参考,或在此处发表评论。我很乐意为您提供帮助!

如果您喜欢这篇文章,请在 Twitter 上关注我@georgedoescode和/或在 CodePen 上关注我 @georgedoescode

你也可以给我买杯咖啡来支持我的教程☕

我总是发布一些小型生成实验,并计划从现在开始每两周发布一篇文章。


后续步骤➡️

从这一点来看,您可以从很多地方进行改进:动画、不同的眼睛类型、不同的身体形状、自定义 blob 生成器等,我很想看到您最终创造出的任何东西!

如果您最终创建了任何想要分享的内容,请将主题标签添加#generativeBlobStuff到您的 CodePens / 推文 / 任何内容中!

非常感谢您的阅读!

鏂囩珷鏉ユ簮锛�https://dev.to/georgedoescode/tutorial-generative-blob-characters-using-svg-1igg
PREV
我应该要求多少薪水?
NEXT
使用 SVG + JS 构建一个平滑的动画 blob