使用 SVG 生成 blob 字符!
这世上我最爱的两件事:圆润的形状和在物体上画活动眼珠。本教程结合了我两大爱好,希望能在创作过程中,为大家带来生成艺术的入门教程。
以下是我们将要制作的内容:
这是一个相当简单的例子,展示了通过生成设计/艺术方法可以实现的目标,但希望您可以对其进行扩展。
先决条件ℹ️
无需任何生成艺术知识!本教程非常适合熟悉 JavaScript / HTML / CSS 并希望开始学习生成艺术的人士。
什么是生成艺术?🤔
我在泰特美术馆网站上找到了关于生成艺术的最简单的定义——
“生成艺术是使用预定系统创作的艺术,通常包含偶然因素——通常应用于基于计算机的艺术”
我认为这是完美的,并且在我们学习本教程的过程中值得牢记,特别是如果您对这些内容还不熟悉。
开始构建吧!🔨
在本教程中,我们将使用 SVG 来渲染我们的角色,使用 JavaScript 来决定渲染什么,并使用少量 CSS 来确保它们能够很好地显示在页面上。
我还添加了几个外部 JS 库,以使我们的代码保持简单和干净。
- https://svgjs.dev/docs/3.0/(用于添加/删除/修改 SVG 元素,例如
<circle>
) - https://www.npmjs.com/package/@georgedoescode/spline(用于通过多个点绘制平滑曲线)
我已经创建了一个 CodePen,你可以在这里fork ,里面已经预先添加了所有这些东西。一旦你 fork 了 CodePen 或设置好了环境,我们就可以开始创建角色了!
包安装
如果您从头开始创建自己的环境而不是分叉 CodePen,则可以使用以下命令安装所需的软件包:
npm install @georgedoescode/spline @svgdotjs/svg.js
然后可以将它们导入到你的 JavaScript 中,如下所示:
import { spline } from "@georgedoescode/spline";
import { SVG } from "@svgdotjs/svg.js";
注意:如果您计划设置自己的环境,请记住您可能需要 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;
}
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
}
}
一旦我们定义了类构造函数,我们就可以通过调用以下命令创建一个全新的 Blob 角色:
// viewBox w x h, target to append the <svg /> element to
const character = new BlobCharacter(200, 200, document.body);
您现在还看不到任何东西,这很酷。
注意: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);
}
使用这个非常方便的实用函数,我们可以为我们的角色定义一个随机大小,如下所示:
class BlobCharacter {
constructor(width, height, target) {
...
// choose a random size / radius for our character
this.size = random(50, 80);
}
}
完美!我们仍然看不到任何东西,但代码看起来不错。
画身体✏️
我认为下一步应该画出角色的身体。这可能是本教程中最棘手的部分,但如果有点困惑,也不用担心!
慢慢来,尝试各种代码,将其分解,再重新组合。
我发现在生成艺术中,我经常会用一段代码一段时间才能真正理解它。我觉得这没关系,没人会因为你精美的作品而看出你对数学知识一窍不通。如果它看起来很酷,那就很酷。我们是在创作艺术,不是在发表研究论文!
无论如何,进入代码...
为了绘制角色的身体,我们将执行以下操作:
- 绘制圆周上的等距点
{x, y}
为每个点的值添加一点随机性- 画一条经过所有点的平滑曲线
可以将此代码添加到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');
}
一旦添加到类中,就可以像这样调用它:
character.drawBody();
好!鼓声响起……
如果你查看浏览器/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')
}
现在这还不能做太多事情。我们来添加另一个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);
}
}
然后我们可以drawEyes()
用同样的方式调用drawBody()
:
character.drawEyes()
现在看看浏览器,应该能看到之前那个很棒的泡泡体,不过还加了几个新的活动眼珠(或者一只)。真棒!
现在是检查 DOM 的好时机,仔细查看<svg />
包含 blob 字符所有部分的元素。你应该看到类似这样的内容:
这是使用 SVG 进行生成艺术的一大优点。由于你有一个可视化的 DOM 树可供探索,因此调试/可视化变得非常容易。
检查元素应该能突出显示一直以来为我们做的<svg />
事情。它只是简化了 SVG DOM 元素的动态创建/更新。如果没有库的话,这可能会非常冗长。svg.js
是时候拿出蜡笔了🖍️
我们的角色看起来棒极了。它个性十足,但我觉得加点颜色会很酷。不过,如果你喜欢,也可以保留黑白。这样就有一种酷酷的卡哇伊素描风格。
这里引入一些颜色的简单方法是定义一个primaryColor
、一个lightColor
要替换的#fff
和一个darkColor
要替换的#000
。
和darkColor
的lightColor
值都用 进行了着色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%)`;
}
我总是使用 HSL 来表示颜色,因为我发现它直观易用,并且在生成环境中易于修改。如上面的代码片段所示,我选择了随机的 H/S/L 值,并使用 JavaScript 模板字符串将它们组合起来。
然后我们可以调用setColors()
BlobCharacter 构造函数:
class BlobCharacter {
constructor(width, height, target) {
...
this.setColors();
}
}
一旦定义了颜色,我们就可以在整个代码中应用它们:
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;
}
}
你的角色现在看起来应该类似于下图。记住,颜色和形状每次都会有所不同!
多样性是生活的调味剂🌶️
我们的角色设计完成了!不过还有最后一件事可以补充……
目前我们只看到一个角色示例。如果能更详细地展示一下这件作品的生成性就更好了。我们可以通过定期重新生成和渲染角色来实现。
为了做到这一点,我们将所有绘图功能包装到类中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();
}
然后,我们可以在创建实例后直接调用此方法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);
希望您现在能看到类似这样的内容...

就这些啦,朋友们!👋
哇,我们成功了!希望你完成了本教程,并创建了一个很酷的角色,同时学习了一些关于生成艺术的知识。
如果您遇到任何困难,请查看最终的示例代码作为参考,或在此处发表评论。我很乐意为您提供帮助!
如果您喜欢这篇文章,请在 Twitter 上关注我@georgedoescode和/或在 CodePen 上关注我 @georgedoescode。
你也可以给我买杯咖啡来支持我的教程☕
我总是发布一些小型生成实验,并计划从现在开始每两周发布一篇文章。
后续步骤➡️
从这一点来看,您可以从很多地方进行改进:动画、不同的眼睛类型、不同的身体形状、自定义 blob 生成器等,我很想看到您最终创造出的任何东西!
如果您最终创建了任何想要分享的内容,请将主题标签添加#generativeBlobStuff
到您的 CodePens / 推文 / 任何内容中!
非常感谢您的阅读!
鏂囩珷鏉ユ簮锛�https://dev.to/georgedoescode/tutorial-generative-blob-characters-using-svg-1igg