refactor(web): 使用 DiceBear 生成默认头像
This commit is contained in:
parent
948a5fda9e
commit
63285b8068
@ -1,7 +1,7 @@
|
|||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import { generatePixelAvatar } from '../pixelAvatar.js'
|
import { generatePixelAvatar } from '../pixelAvatar.js'
|
||||||
|
|
||||||
const decodeAvatarSvg = (avatar) => decodeURIComponent(avatar.replace('data:image/svg+xml,', ''))
|
const DICEBEAR_GLYPHS_AVATAR_BASE_URL = 'https://api.dicebear.com/10.x/glyphs/svg'
|
||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
{
|
{
|
||||||
@ -20,15 +20,22 @@ const run = () => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const avatar = generatePixelAvatar('user-003')
|
const avatar = generatePixelAvatar('user-003')
|
||||||
assert.ok(avatar.startsWith('data:image/svg+xml,'), 'Should return an SVG data URL')
|
assert.equal(
|
||||||
console.log('T3 Data URL prefix: PASS')
|
avatar,
|
||||||
|
`${DICEBEAR_GLYPHS_AVATAR_BASE_URL}?seed=user-003`,
|
||||||
|
'Should return a DiceBear glyphs avatar URL'
|
||||||
|
)
|
||||||
|
console.log('T3 DiceBear URL: PASS')
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const svg = decodeAvatarSvg(generatePixelAvatar('user-004'))
|
const avatar = generatePixelAvatar(' user/中文 ')
|
||||||
assert.ok(svg.includes('<svg'), 'Decoded output should contain an SVG tag')
|
assert.equal(
|
||||||
assert.ok(svg.includes('<rect'), 'Decoded output should contain pixel rects')
|
avatar,
|
||||||
console.log('T4 Decodable SVG: PASS')
|
`${DICEBEAR_GLYPHS_AVATAR_BASE_URL}?seed=user%2F%E4%B8%AD%E6%96%87`,
|
||||||
|
'Seed should be trimmed and URL encoded'
|
||||||
|
)
|
||||||
|
console.log('T4 Encoded seed: PASS')
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,20 +1,4 @@
|
|||||||
const DEFAULT_SIZE = 64
|
const DICEBEAR_GLYPHS_AVATAR_BASE_URL = 'https://api.dicebear.com/10.x/glyphs/svg'
|
||||||
const GRID_SIZE = 5
|
|
||||||
const MIRROR_COLUMNS = 3
|
|
||||||
const DEFAULT_BACKGROUND = '#f3f4f6'
|
|
||||||
|
|
||||||
const COLOR_PALETTE = [
|
|
||||||
'#265c96',
|
|
||||||
'#3996ae',
|
|
||||||
'#13c2c2',
|
|
||||||
'#52c41a',
|
|
||||||
'#faad14',
|
|
||||||
'#ff7a45',
|
|
||||||
'#ff4d4f',
|
|
||||||
'#9254de',
|
|
||||||
'#597ef7',
|
|
||||||
'#4f4f4f'
|
|
||||||
]
|
|
||||||
|
|
||||||
const normalizeSeed = (id) => {
|
const normalizeSeed = (id) => {
|
||||||
if (id === null || id === undefined || String(id).trim() === '') {
|
if (id === null || id === undefined || String(id).trim() === '') {
|
||||||
@ -23,52 +7,7 @@ const normalizeSeed = (id) => {
|
|||||||
return String(id).trim()
|
return String(id).trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashSeed = (seed) => {
|
|
||||||
let hash = 2166136261
|
|
||||||
|
|
||||||
for (let i = 0; i < seed.length; i += 1) {
|
|
||||||
hash ^= seed.charCodeAt(i)
|
|
||||||
hash = Math.imul(hash, 16777619)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash >>> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildCells = (hash) => {
|
|
||||||
const cells = []
|
|
||||||
|
|
||||||
for (let row = 0; row < GRID_SIZE; row += 1) {
|
|
||||||
for (let col = 0; col < MIRROR_COLUMNS; col += 1) {
|
|
||||||
const bitIndex = row * MIRROR_COLUMNS + col
|
|
||||||
const filled = ((hash >>> bitIndex) & 1) === 1
|
|
||||||
|
|
||||||
if (!filled) continue
|
|
||||||
|
|
||||||
cells.push([col, row])
|
|
||||||
|
|
||||||
const mirroredCol = GRID_SIZE - col - 1
|
|
||||||
if (mirroredCol !== col) {
|
|
||||||
cells.push([mirroredCol, row])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cells.length === 0) {
|
|
||||||
cells.push([2, 2])
|
|
||||||
}
|
|
||||||
|
|
||||||
return cells
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generatePixelAvatar = (id) => {
|
export const generatePixelAvatar = (id) => {
|
||||||
const seed = normalizeSeed(id)
|
const seed = normalizeSeed(id)
|
||||||
const hash = hashSeed(seed)
|
return `${DICEBEAR_GLYPHS_AVATAR_BASE_URL}?seed=${encodeURIComponent(seed)}`
|
||||||
const color = COLOR_PALETTE[(hash >>> 16) % COLOR_PALETTE.length]
|
|
||||||
const cells = buildCells(hash)
|
|
||||||
.map(([x, y]) => `<rect x="${x}" y="${y}" width="1" height="1"/>`)
|
|
||||||
.join('')
|
|
||||||
|
|
||||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${DEFAULT_SIZE}" height="${DEFAULT_SIZE}" viewBox="0 0 ${GRID_SIZE} ${GRID_SIZE}" shape-rendering="crispEdges"><rect width="${GRID_SIZE}" height="${GRID_SIZE}" fill="${DEFAULT_BACKGROUND}"/><g fill="${color}">${cells}</g></svg>`
|
|
||||||
|
|
||||||
return `data:image/svg+xml,${encodeURIComponent(svg)}`
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user