Skip to content

rockerdance/next-api-share

 
 

Repository files navigation

声明

本文的头像生成部分参考了项目 txstc55/ugly-avatar

效果展示

体验地址

demo 体验地址

源码地址

github 地址

项目背景

在浏览社区论坛时,我注意到许多相似的头像,它们虽然并不美观,但却具有鲜明的特点。通过查阅评论,我发现这些头像都是由一个开源的头像生成工具制作的。本文中的头像生成部分,将参考该项目进行讨论。

做了哪些优化

  • 增设了 API 接口,可以直接返回随机生成的头像;
  • 添加了 'id' 和 'username' 参数,通过固定这两个参数,可以确保返回的头像保持一致;
  • 增加了 'bg_color' 参数,通过这个参数,可以确保头像的背景色是固定的颜色值;
  • 增加了 'w' 和 'h' 参数,通过这个参数,可以指定返回图片的宽度和高度;

api demo

随机返回

https://next-api-share.vercel.app/api/face

固定背景色

https://next-api-share.vercel.app/api/face?bg_color=rgb(245,245,220)

https://next-api-share.vercel.app/api/face?bg_color=red

固定返回值

https://next-api-share.vercel.app/api/face?id=666

https://next-api-share.vercel.app/api/face?username=john

固定宽度或高度

https://next-api-share.vercel.app/api/face?w=400&h=400

技术细节

头像合成

在这个项目中,最核心的部分就是如何生成一个头像图片。这里通过SVG(可缩放矢量图形)来生成的。将整个头像拆分成多个部分,包括脸型、眼睛、鼻子、嘴巴和头发。然后会随机生成这些部分,并随机组合它们。为了保证这些组件在整体上的位置符合逻辑,进行了一些数学计算。这样,每次生成的头像都会有其独特的特点,同时又保持了一定的逻辑性和协调性。

<svg viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg" width="500" height="500" id="face-svg">
  <g id="mouth">
    <polyline id="faceContour" :points="computedFacePoints" fill="#ffc9a9" stroke="black"/>
  </g>
  <!-- ... -->
  <g id="hairs">
    <polyline v-for="(hair, index) in hairs" :key="index" :points="hair" fill="none" :stroke="hairColor"      :stroke-width="2" stroke-linejoin="round" filter="url(#fuzzy)" />
  </g>
  <!-- ... -->
  <g id="mouth">
    <polyline :points="mouthPoints" fill="rgb(215,127,140)" stroke="black" :stroke-width="3" stroke-linejoin="round"
      filter="url(#fuzzy)" />
  </g>
</svg>

这段代码就是动态生成和渲染头像的各个部分的核心代码。

  • <svg>标签定义了一个SVG的画布,viewBox属性定义了视图框的大小和视图框内的坐标系统,widthheight属性定义了SVG的宽度和高度;
  • <g>标签是一个容器元素,用于将多个元素组合在一起;在这里,它被用来分组和标识头像的不同部分,如头发(hairs)和嘴巴(mouth);
  • <polyline>标签用于创建只有直线段或曲线段的形状;在这里,它被用来绘制头像的脸部轮廓(faceContour)头发(hairs)嘴巴(mouth):points属性定义了多边形的所有顶点;

如何将 vue3 组件移植到服务端返回

在服务端,我们同样可以采用 Vue 框架。通过 Vue,我们能够将渲染结果生成为 HTML 文本并返回:

export const getSvg = async () => {
  // 获取渲染数据
  const data = getImageData();
  const app = createSSRApp({
    template: '<svg>xxxxx</svg>',
    data,
  })
  let svgData = await renderToString(app)
  return svgData;
}

在这段代码中,我们首先获取了渲染所需的数据。然后,我们创建了一个服务端渲染应用,并将获取到的数据传入。最后,我们将应用渲染为字符串,并返回这个字符串。

如何返回图片类型

如何返回图片类型的数据,以及前端如何解析返回的值,主要取决于响应头(response header)中的 Content-Type 值。SVG 是一种文本类型,如果我们希望前端将返回的文本视为 SVG 类型的图片,就需要将 Content-Type设置为 image/svg+xml;

const svg = `<svg>xxxx</svg>`
res.status(200).setHeader('Content-Type', 'image/svg+xml').send(svg);

我们首先通过计算得到一个 SVG 图片。然后,我们设置了响应的状态码为 200,同时设置了响应头的 Content-Typeimage/svg+xml,最后我们将 SVG 图片发送给前端。

如何实现对于 id 和 username, 返回的头像是相同的

在生成头像的代码中,我们大量使用了 Math.random() 来实现每次生成的头像都不同的效果。然而,Math.random() 生成的是伪随机数,只要固定了种子,生成的随机数就会是固定的。 在这里,我们直接采用了 idusername 作为随机数的种子。这样,对于同一个 idusername,每次生成头像时,结果都会是相同的。

const { id, username } = req.query;
const seed = (id || username || `${Math.random()}`) as string
const rng = seedrandom(seed);
const result = await getSvg(rng);

其他细节

组件属性大小写问题

viewBox 是一个重要的属性,它定义了 SVG 图形的可视区域; 为了解决这个问题,我们可以在生成 SVG 数据后,通过字符串替换的方式,将 viewbox 替换为正确的 viewBox

svgData = svgData.replace('viewbox', 'viewBox')

TODO

  • 扩展对更多图片格式的支持,例如 PNG 和 WebP;
  • 引入更多参数,如 hairColor,以确保背景色能够保持固定的颜色值;

参考

ugly-avatar

Getting Started

First, run the development server:

npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev

Open http://localhost:3000 with your browser to see the result.

Learn More

Deploy on Vercel

如果你没有自己的服务端,你可以免费的部署在 vercel 上。

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 87.9%
  • Vue 8.7%
  • CSS 2.7%
  • JavaScript 0.7%