使用 SVG 创建生成社交图像!
最终结果
但是...它到底是用来做什么的?
升空🚀
2021年了,网络世界充满了各种奇异、恐怖、美丽的事物。该如何确保你的网站脱颖而出呢?
好吧,除了一些精彩的内容之外,我认为一个很棒的生成社交图像(就像本教程中使用的图像一样!)可能是一个很好的一步✨
我们来做一些吧!
最终结果
首先,让我们直接跳到最后。这是本教程的最终结果:
这是一个可扩展、可编辑、可自动生成的社交图片!点击上方 CodePen 中的按钮或更改文本内容,就能看到图片神奇地重新设计。🔮
但是...它到底是用来做什么的?
我称之为“社交图像”或“元图像”,是您粘贴链接时在 Slack / Twitter / Facebook 中显示的小预览。
以下是在野外发现的一些社会图像示例……
Stephanie Eckles的精美纹理设计:
DEV + Ben Halpern备受喜爱的社交形象:
Josh Comeau的一些非常酷的 3D 氛围:
虽然我所有的例子都来自 Twitter,但重要的是要记住(使用 SVG 创建社交图像的巨大好处)不同的网站可能需要不同的尺寸。
SVG
幸运的是,借助+的强大功能viewBox
,我们在本教程中创建的图像可以轻松调整为任意尺寸/纵横比。太棒了!
升空🚀
好了,我想开场白就到这里。我们准备开始建造了。各位,穿上工装裤!
HTML 标记
首先让我们为页面添加一些 HTML:
<div class="wrapper">
<div class="social-image-wrapper">
<!-- Save a space for our SVG! -->
</div>
<div class="controls">
<div class="controls__randomize">
<p class="controls__label">Randomize:</p>
<button class="controls__btn controls__btn--alignment">Alignment</button>
<button class="controls__btn controls__btn--colors">Colors</button>
<button class="controls__btn controls__btn--shapes">Shapes</button>
</div>
<button class="controls__btn controls__btn--save">Save</button>
</div>
</div>
在此代码片段中,我们添加了 UI 所需的 HTML 标记,并将所有内容弹出到一个漂亮的小包装器中div
。
SVG 标记
添加完用户界面的 HTML 代码后,接下来就是主要标记事件了。我之前提到过,社交图片将使用<svg>
元素创建,所以让我们在 中添加一个social-image-wrapper
:
<div class="social-image-wrapper">
<svg
viewBox="0 0 1200 630"
xmlns="http://www.w3.org/2000/svg"
class="social-image"
>
<foreignObject x="0" y="0" width="1200" height="630">
<div class="social-image__html">
<div class="social-image__text">
<h1
xmlns="http://www.w3.org/1999/xhtml"
class="social-image__title"
contenteditable
>
All of this text is editable... click on it and start typing!
</h1>
<h2
xmlns="http://www.w3.org/1999/xhtml"
class="social-image__meta"
contenteditable
>
As you type, the background will adapt itself to the text, making
sure the shapes never overlap.
</h2>
</div>
</div>
</foreignObject>
</svg>
</div>
这里有不少东西要解开,不过别担心!我们可以一起解决🤝
视图框
首先,我们创建<svg>
元素并定义一个viewBox:
<svg
viewBox="0 0 1200 630"
xmlns="http://www.w3.org/2000/svg"
class="social-image"
>
...
</svg>
该属性定义了我们将在其中绘制viewBox
所有内容的坐标空间。在我们的例子中,它是。<svg>
1200x630px
借助 的功能viewBox
,我们可以相对于固定的坐标空间定位/缩放所有内容,同时<svg>
本身也可以缩放到任意大小。功能强大 ⚡
外部对象
接下来,我们foreignObject
向元素添加一个填充了一些 HTML 的标签<svg>
:
<foreignObject x="0" y="0" width="1200" height="630">
...
</foreignObject>
这就是事情开始变得有趣的地方!foreignObject可用于将来自另一个 XML 命名空间(在我们的例子中是 HTML)的内容添加到<svg>
元素。
添加后,它将HTML
自动缩放到viewBox
与常规内容一样的大小SVG
。此功能非常强大,因为它允许我们使用 CSS 来设置社交图片内容的样式,同时保留 SVG 的流畅性和渲染能力。
稍后我们会对此进行详细介绍。
注意:HTML
添加的任何元素都foreignObject
必须具有xmlns="http://www.w3.org/1999/xhtml"
属性。
contenteditable 属性
这里最后要检查的是contenteditable
添加到我们的h1
和h2
标签的属性:
<h1
xmlns="http://www.w3.org/1999/xhtml"
class="social-image__title"
contenteditable
>
All of this text is editable... click on it and start typing!
</h1>
contenteditable仅允许用户编辑标签内的文本HTML
。这对我们来说非常完美,因为这意味着用户可以轻松添加自己的内容并立即预览结果。
时尚时刻💅
好的,现在我们已经有了创建精美社交图片所需的所有标记。不过,看起来可能有点不太好。我们真的应该解决这个问题。
页面样式
首先,让我们为 UI 添加一些样式:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--black: hsl(0, 0%, 10%);
}
body {
width: 100vw;
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: var(--black);
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wrapper {
width: 100%;
max-width: 60rem;
min-width: 20rem;
margin: 0 auto;
overflow: hidden;
}
.controls {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 2rem 0;
}
.controls__label {
margin-right: 1rem;
font-weight: 500;
font-size: 1rem;
}
.controls__randomize {
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
}
.controls__btn {
width: 8rem;
height: 2.25rem;
margin-right: 1rem;
background: #fff;
border-radius: 0;
border: none;
border: 2px solid var(--black);
font-family: inherit;
color: var(--black);
font-size: 1rem;
font-weight: 500;
cursor: pointer;
}
.controls__btn:hover {
background: var(--black);
color: #fff;
}
.controls__btn--save {
position: relative;
margin-left: auto;
margin-right: 0;
background: var(--black);
color: #fff;
}
.controls__btn--save:hover {
background: #fff;
color: var(--black);
}
.controls__saving-disabled {
font-size: 0.875rem;
margin-top: 2rem;
font-weight: 500;
display: none;
font-style: italic;
}
@media only screen and (max-width: 800px) {
body {
padding: 0.75rem;
}
.controls__btn {
width: 6rem;
height: 2rem;
font-size: 0.875rem;
margin-top: 0.75rem;
}
.controls__label {
font-size: 0.875rem;
margin-right: 0.5rem;
width: 100%;
}
.controls__btn--save {
width: 100%;
margin-top: 1.25rem;
}
}
@media only screen and (max-width: 480px) {
.controls__btn {
margin-right: 0.5rem;
}
}
我不会深入讲解这些 CSS,因为它不是本文的主要功能。如果您对这些样式有任何疑问,请随时给我留言。
社交形象风格
接下来,让我们在元素<style>
中添加一个内部标签<svg>
。它将包含社交图片本身的所有样式:
<svg
viewBox="0 0 1200 630"
xmlns="http://www.w3.org/2000/svg"
class="social-image"
>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.social-image {
--align-text-x: flex-start;
--align-text-y: flex-end;
width: 100%;
background: #f5f7fa;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
line-height: 1;
}
.social-image__html {
display: flex;
height: 100%;
justify-content: var(--align-text-x);
align-items: var(--align-text-y);
padding: 72px;
}
.social-image__text {
max-width: 700px;
}
.social-image__title {
font-size: 56px;
line-height: 68px;
font-weight: 800;
margin-bottom: 24px;
letter-spacing: -0.0125em;
outline: none;
}
.social-image__meta {
font-weight: 500;
font-size: 24px;
line-height: 36px;
outline: none;
letter-spacing: -0.0125em;
}
</style>
...
</svg>
我们将此 CSS 添加到内部<style>
标签中,因为我遇到了一些问题,导致html2canvas
样式在标签外部无法按预期渲染<svg>
。这样也能很好地控制内容。
再次强调,我不会在这里详细介绍 CSS,但此样式表的主要效果是:
-
设置一些CSS 自定义属性,结合flexbox来处理社交图片中文本的位置。我们可以稍后使用 JavaScript 修改这些自定义属性。
-
为文本内容添加一些排版样式。我们这里使用的是系统字体。也可以使用自定义字体,但这样做会增加一些复杂性,因为字体需要嵌入到 中
<svg>
。下次再说吧!
我们迄今为止的进展
现在这两个样式表都已添加到各自的位置,您应该会在浏览器中看到类似这样的内容:
很酷吧!当你调整浏览器大小时,看看我们的 HTML 是如何神奇地随着<svg>
元素缩放的 ✨
至此,一切准备就绪,一切美好即将到来。让我们前往 JS-town,实现梦想吧🎨
下一站,JavaScript 中心🚂
包安装
我们先把无聊的事情处理掉,安装这个项目所需的软件包。我们将要使用的软件包是:
- svg.js - 用于简化 SVG 脚本(创建和更新 SVG 元素,例如
<circle>
) - html2canvas - 用于截取我们的
<svg>
社交图片的屏幕截图,以便下载 - file-saver - 用于处理社交图像被捕获后的保存
html2canvas
- resize-observer-polyfill
ResizeObserver
- 为不支持它的浏览器添加 polyfill
如果您正在关注 CodePen,您可以简单地将这些导入添加到您的 JS 文件中:
import { SVG } from "https://cdn.skypack.dev/@svgdotjs/svg.js";
import html2canvas from "https://cdn.skypack.dev/html2canvas@1.0.0-rc.7";
import ResizeObserver from "https://cdn.skypack.dev/resize-observer-polyfill@1.5.1";
import FileSaver from "https://cdn.skypack.dev/file-saver@2.0.5";
如果您在自己的环境中工作,则可以使用以下命令安装所需的软件包:
npm i svgjs html2canvas resize-observer-polyfill file-saver
然后可以像这样导入包:
import { SVG } from "svg.js";
import html2canvas from "html2canvas";
import ResizeObserver from "resize-observer-polyfill";
import FileSaver from "file-saver";
注意:如果您在自己的环境中工作,则需要 Webpack 或 Parcel 等捆绑器来处理这些导入。
DOM 元素引用
现在我们已经拥有了该项目所需的所有包,我们应该添加一些引用各种 DOM 元素(按钮、社交图像 svg 等)的变量
为此,我们可以添加:
const socialImageSVG = document.querySelector(".social-image");
const socialImageTitle = document.querySelector(".social-image__title");
const socialImageMeta = document.querySelector(".social-image__meta");
const saveBtn = document.querySelector(".controls__btn--save");
const alignmentBtn = document.querySelector(".controls__btn--alignment");
const colorBtn = document.querySelector(".controls__btn--colors");
const shapesBtn = document.querySelector(".controls__btn--shapes");
颜色
接下来是定义一些颜色变量。这些变量将存储一些 HSL 颜色,我们稍后会定义它们,并最终用于为我们的社交形象着色:
let baseColor;
let baseColorWhite;
let baseColorBlack;
let complimentaryColor1;
let complimentaryColor2;
let shapeColors;
很好。现在所有颜色都是空的,不过没关系。
对齐选项
除了随机颜色之外,我们的社交图片还允许文本随机对齐。为了进一步简化操作,我们将flex
用于控制对齐的属性存储在一个数组中:
const alignmentOpts = ["flex-start", "flex-end", "center"];
太棒了!我们很快就会用到这些值。
设置 svg.js 实例
我们将在这里使用svg.js来实现快速、简单的 SVG 脚本编写。如果没有 svg.js,创建和更新 SVG 元素将会非常繁琐。
我们可以像这样创建一个新的 svg.js 实例:
const shapes = SVG(socialImageSVG).group();
这行代码的意思是——<group>
在我们的根元素中创建一个新的 SVG 元素,<svg>
我可以使用诸如 之类的方法轻松地在其中绘制shapes.rect(...)
。
添加random()
实用功能
在我们继续之前,让我们快速添加一个小的实用函数random
,它会生成一个范围内的随机数:
function random(min, max) {
return Math.random() * (max - min) + min;
}
这工具超级好用。如果你想尝试一些更具生成性的东西,一定要把它留着以后用!我一直都在用它。
选择一些随机颜色
在我的教程中,我通常会把颜色留到最后,但我觉得这次我们应该早点定义它们。它们是最终结果不可或缺的一部分,设置好颜色会让接下来的步骤更容易理解。
为了生成一些随机颜色,我们可以添加以下setColors
函数:
function setColors() {
const baseHue = random(0, 360);
const saturation = random(60, 90);
baseColor = `hsl(${baseHue}, ${saturation}%, 60%)`;
baseColorWhite = `hsl(${baseHue}, ${saturation}%, 97%)`;
baseColorBlack = `hsl(${baseHue}, 95%, 3%)`;
complimentaryColor1 = `hsl(${baseHue + 90}, ${saturation}%, 60%)`;
complimentaryColor2 = `hsl(${baseHue + 180}, ${saturation}%, 60%)`;
shapeColors = [complimentaryColor1, complimentaryColor2, baseColor];
socialImageSVG.style.background = baseColorWhite;
socialImageSVG.style.color = baseColorBlack;
}
该函数的作用如下:
- 选择一个随机色调,介于 0 到 360 之间
- 选择一个随机饱和度,介于 60 到 90 之间
- 定义一个基色、一个非常深的颜色和一个非常浅的颜色,所有颜色都基于相同的色调。这是创建简单调色板并保持一致性的好方法。
- 选择两种互补色,每种颜色的色相与前一种颜色成90度角,且饱和度和亮度相同。这是另一种简单有效的找到搭配颜色的方法。
- 将补色和底色存储在
shapeColors
数组中。稍后我们将用它们来填充形状 - 将社交图片的背景设置为非常浅的颜色,并将其文本颜色设置为非常深的颜色
现在,如果我们调用setColors()
,我们应该会看到社交图片的背景和文本颜色发生变化。变化会非常微妙。希望是这样的:
看起来不错。继续!
创建随机形状位置
接下来,我们需要生成一些随机的、不重叠的矩形,用于放置形状。我们希望这些矩形不仅避免彼此重叠,也避免与文本重叠。
一个小问题
为了避免在创建随机矩形时文本重叠,我们需要知道每个文本元素相对于我们的的<svg>
尺寸viewBox
。
通常为了这个目的我们会使用getBBox但是getBBox
它只适用于 SVG 元素,而我们的文本是 HTML。
这并不是那么糟糕,我们可以创建自己的relativeBounds
函数来立即解决这个问题!
这里是:
function relativeBounds(svg, HTMLElement) {
const { x, y, width, height } = HTMLElement.getBoundingClientRect();
const startPoint = svg.createSVGPoint();
startPoint.x = x;
startPoint.y = y;
const endPoint = svg.createSVGPoint();
endPoint.x = x + width;
endPoint.y = y + height;
const startPointTransformed = startPoint.matrixTransform(
svg.getScreenCTM().inverse()
);
const endPointTransformed = endPoint.matrixTransform(
svg.getScreenCTM().inverse()
);
return {
x: startPointTransformed.x,
y: startPointTransformed.y,
width: endPointTransformed.x - startPointTransformed.x,
height: endPointTransformed.y - startPointTransformed.y
};
}
太棒了!我不会深入讲解这个函数,因为我知道它比较枯燥,但它本质上为我们提供了getBBox
在 SVG 中使用 HTML 元素的功能。
现在我们有了relativeBounds
函数,我们可以生成形状位置。
让我们添加一个generateRandomRects
和一个detectRectCollision
函数:
function generateRandomRects(existing) {
const rects = [...existing];
const tries = 250;
const maxShapes = 6;
for (let i = 0; i < tries; i++) {
if (rects.length === maxShapes + existing.length) break;
const size = random(100, 600);
const rect = {
x: random(-size, 1200),
y: random(-size, 630),
width: size,
height: size
};
if (!rects.some((r) => detectRectCollision(r, rect))) {
rects.push(rect);
}
}
return rects;
}
function detectRectCollision(rect1, rect2, padding = 32) {
return (
rect1.x < rect2.x + rect2.width + padding &&
rect1.x + rect1.width + padding > rect2.x &&
rect1.y < rect2.y + rect2.height + padding &&
rect1.y + rect1.height + padding > rect2.y
);
}
具体来说:
- 将一些现有的矩形存储在数组中(在我们的例子中,是文本元素的周围矩形或边界)
- 对于一定次数的尝试:创建一个随机大小的矩形。如果这个新矩形与任何其他矩形均不重叠,则将其存储。
- 一旦所有尝试都用完,或者达到最大形状数量,就返回我们成功生成的随机矩形
你可能会注意到padding
矩形碰撞代码中有一个看起来很奇怪的选项。它定义了矩形之间的最小距离。我发现它能让代码看起来更简洁一些。
关于不完美的说明
这远非一个完美的函数。由于使用蛮力来放置矩形,因此速度相当慢,而且我们无法保证maxShapes
多次尝试后一定能达到目标。
但这是否意味着它不好呢?绝对不是。
我们现在更关心的是视觉效果,而不是算法效率,而这些值似乎能产生相当美观的效果。生成式设计的真正挑战在于对这类值的调整。
你应该尝试改变这些参数。尝试改变最大形状数量,或者调整我们的尺寸,或者增加最大尝试次数。看看结果。重复。这里没有正确答案!
绘制我们的形状
好了,我们已经准备好一些代码来生成不重叠的矩形。让我们把它们变成现实!
首先,让我们添加一个新generate
功能:
function generate() {
shapes.clear();
const htmlRects = [
relativeBounds(socialImageSVG, socialImageTitle),
relativeBounds(socialImageSVG, socialImageMeta)
];
const rects = generateRandomRects(htmlRects);
for (const rect of rects.slice(2, rects.length)) {
drawRandomShape(rect);
}
}
这实际上是一小段代码。generateRandomRects
这里完成了大部分繁重的工作。我们说的是:
- 清除任何已经存在的形状(这在以后动态重新生成图像时很有用)
- 将两个文本元素相对于的边界存储
viewBox
在一个数组中 - 生成一堆随机、不重叠的矩形
- 对于每个随机矩形(除前两个文本矩形之外)在其中绘制一个随机形状。
现在,我们实际上还没有drawRandomShape
函数。让我们添加一个。作为一个简单的开始,请尝试以下代码:
function drawRandomShape(rect) {
const { x, y, width, height } = rect;
shapes.rect(width, height).x(x).y(y);
}
添加后drawRandomShape
,您可以安全地调用,generate
而不会让浏览器生气:
generate();
如果你现在检查浏览器,你应该会看到类似这样的内容:
太棒了!这些就是我们之前生成的随机矩形,以一种非常简单的方式呈现。
不过,我们可以扩展。让我们更新drawRandomShape
并添加一个小的randomColor
实用函数:
function randomColor() {
// ~~ === shorthand for Math.floor()
return shapeColors[~~random(0, shapeColors.length)];
}
function drawRandomShape({ x, y, width, height }) {
const shapeChoices = ["rect", "ellipse", "triangle"];
let shape;
switch (shapeChoices[~~random(0, shapeChoices.length)]) {
case "ellipse":
shape = shapes.ellipse(width, height).x(x).y(y);
break;
case "triangle":
shape = shapes
.polygon(`0 ${height}, ${width / 2} 0, ${width} ${height}`)
.x(x)
.y(y);
break;
default:
shape = shapes.rect(width, height).x(x).y(y);
}
const color = randomColor();
if (random(0, 1) > 0.25) {
shape.fill(color);
} else {
shape
.stroke({
color,
width: 16
})
.fill("transparent");
}
shape.node.classList.add("shape");
shape.rotate(random(0, 90)).scale(0.825);
shape.opacity(random(0.5, 1));
}
以下是这里发生的事情的详细说明:
- 选择随机形状类型
- 使用 svg.js 根据我们的形状选择渲染不同的 SVG 元素
- 从我们之前定义的选项中随机选择一种颜色
- 25% 的情况下,将此颜色应用于形状轮廓。其余 75% 的情况下,使用此颜色填充形状
shape
给元素添加一个类,以便我们以后可以快速引用它- 将形状旋转某个随机值,并将其不透明度降低一个随机量
呼!事情变得相当紧张了。让我们休息一下,惊叹一下我们精彩的创作!
哇!🤩 看起来不错,各位。我们差不多完成了。刷新浏览器,每次都会看到不同的内容。
交互性
本教程的最后一步是实现交互。这主要涉及将事件监听器附加到内容上,并运行我们已经定义的功能。
为了简洁起见,我已经在代码中添加了注释。如果您需要更多详细信息或对此有任何疑问,请随时告诉我!
连接按钮
// regenerate our shapes and shape positions
shapesBtn.addEventListener("click", () => {
generate();
});
// set new random color values and update the existing shapes with these colors
colorBtn.addEventListener("click", () => {
setColors();
// find all the shapes in our svg and update their fill / stroke
socialImageSVG.querySelectorAll(".shape").forEach((node) => {
if (node.getAttribute("stroke")) {
node.setAttribute("stroke", randomColor());
} else {
node.setAttribute("fill", randomColor());
}
});
});
// choose random new alignment options and update the CSS custom properties, regenerate the shapes
alignmentBtn.addEventListener("click", () => {
socialImageSVG.style.setProperty("--align-text-x", alignmentOpts[~~random(0, alignmentOpts.length)]);
socialImageSVG.style.setProperty("--align-text-y", alignmentOpts[~~random(0, alignmentOpts.length)]);
generate();
});
// save our social image as a .png file
saveBtn.addEventListener("click", () => {
const bounds = socialImageSVG.getBoundingClientRect();
// on save, update the dimensions of our social image so that it exports as expected
socialImageSVG.style.width = "1200px";
socialImageSVG.style.height = "630px";
socialImageSVG.setAttribute("width", 1200);
socialImageSVG.setAttribute("height", 630);
// this fixes an odd visual "cut off" bug when exporting
window.scrollTo(0, 0);
html2canvas(document.querySelector(".social-image-wrapper"), {
width: 1200,
height: 630,
scale: 2 // export our image at 2x resolution so it is nice and crisp on retina devices
}).then((canvas) => {
canvas.toBlob(function (blob) {
// restore the social image styles
socialImageSVG.style.width = "100%";
socialImageSVG.style.height = "auto";
socialImageSVG.setAttribute("width", "");
socialImageSVG.setAttribute("height", "");
FileSaver.saveAs(blob, "generative-social-image.png");
});
});
});
处理新的文本输入
好的,所有按钮都连接好了,太棒了。不过还有最后一个功能需要添加。当用户输入时,我们需要更新形状的位置。为此,我们可以ResizeObserver
在每次文本元素的宽度/高度尺寸发生变化时运行一个函数。
一探究竟:
const resizeObserver = new ResizeObserver(() => {
generate();
});
resizeObserver.observe(socialImageTitle);
resizeObserver.observe(socialImageMeta);
现在,当您输入时,您应该会看到您的社交图像更新,就像 CodePen 示例一样。
我们成功了!
哎呀,真是太棒了!好消息是,我们终于完成了。希望你在这里学到了一些关于生成式设计的知识,甚至学到了一些实用的 SVG 小技巧。
我认为你可以在很多地方采用这个方法,如果你根据本教程创建了一些很酷的东西,我很乐意听到你的意见😎
如果您确实喜欢这篇文章,请在 Twitter 上关注我@georgedoescode,享受源源不断的创意编码乐趣。
你也可以给我买杯咖啡来支持我的教程☕
非常感谢你的阅读!下次再见❤️
文章来源:https://dev.to/georgedoescode/tutorial-create-generative-social-images-using-svg-2c47