🌅 让你的用户头像更具艺术感,实现一个自动生成唯一渐变色的头像组件
前言
这一篇文章依然是组件实现系列,这次我们来实现一个基于用户昵称动态生成用户头像的组件。在很多中后台场景中,用户并不需要自己去上传头像,而是简单的展示一个基本的头像图片,但是这样就太没意思啦。这次头像组件的,预期是能够基于用户昵称字符串生成某种颜色,然后设置为 渐变色背景 并添加一些额外的细节就能展示出一个相对好看的头像。
实现过程
Avatar 组件
首先我们封装一个 Avatar
组件,这里我引用了 ChakraUI 组件库:
const Avatar: React.FC<{ name: string } & AvatarProps> = ({
name,
...rest
}) => {
return (
<Box w="12" h="12" p="0" {...rest}>
<ChakraAvatar {...getGradientStyle(name)} name={name} />
Box>
);
};
export default Avatar;
样式生成函数
这一步还是很简单的,在组件的 props 中我使用了一个 getGradientStyle(name)
函数用于获取头像组件的样式,下面我们来实现这个函数:
const getGradientStyle: (text: string) => AvatarProps = (text) => {
const color1 = getRandomColor(text, 1);
const color2 = getRandomColor(text, 0.7);
return {
backgroundImage: `linear-gradient(135deg, ${color1}, ${color2})`,
color: "white",
display: "flex",
justifyContent: "center",
alignItems: "center",
fontWeight: "bold",
borderRadius: "10px",
background: "transparent",
fontFamily: "Helvetica, Arial, sans-serif",
};
};
随机颜色函数
这个函数返回一个包含渐变和其他属性的样式对象,这里面还有一个核心的函数 getRandomColor
,这个函数可以基于字符串生成颜色,并且可以自己传入透明度,下面说说这个函数是如何实现的:
function getRandomColor(str: string, alpha: number) {
let asciiSum = 0;
for (let i = 0; i < str.length; i++) {
asciiSum += str.charCodeAt(i);
}
const red = Math.abs(Math.sin(asciiSum) * 256).toFixed(0);
const green = Math.abs(Math.sin(asciiSum + 1) * 256).toFixed(0);
const blue = Math.abs(Math.sin(asciiSum + 2) * 256).toFixed(0);
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
}
解析一下 getRandomColor
函数的执行过程:
- 首先遍历字符串
str
中的每个字符,计算它们的 ASCII 码值之和asciiSum
。 - 使用这个数字作为参数,通过
Math.sin
函数生成一个介于 -1 和 1 之间的正弦值。再通过Math.sin
- 将这个正弦值乘以 256 并四舍五入,得到一个介于 0 和 255 之间的整数,作为 rgba 颜色值的红色、绿色、蓝色分量。
- 最后,将传入的透明度
alpha
与颜色值一起组成一个 rgba 颜色值字符串并返回。
实现的效果如下图:
字体阴影
基本的一个头像已经做好了,但是我们还需要补充一些细节。为了让字体在浅色背景下也可以看清楚,我们可以为字体加上一点字体阴影。
textShadow: "1px 1px 3px rgba(0, 0, 0, 0.2)"
添加前后的对比可以看下图,重点是像 "王" 字这种比较浅色的是不是一下就清晰多了。
添加前:
添加后:
背景纹理
现在的头像背景效果已经蛮不错了,但是只是一个渐变背景我还是觉得太单调了,如果里面能够增加一点纹理就好了。于是我又添加了一个水波纹的效果,实现的代码如下:
return {
backgroundImage: `linear-gradient(135deg, ${color1}, ${color2})`,
color: "white",
display: "flex",
justifyContent: "center",
alignItems: "center",
fontWeight: "bold",
borderRadius: "10px",
background: "transparent",
textShadow: "1px 1px 3px rgba(0, 0, 0, 0.2)",
fontFamily: "Helvetica, Arial, sans-serif",
position: "relative",
_before: {
content: `""`,
position: "absolute",
left: "0",
top: "0",
w: "full",
h: "full",
borderRadius: "10px",
backgroundColor: "white",
backgroundImage: `repeating-radial-gradient( circle at 0 0, transparent 0, #ffffff 9px ), repeating-linear-gradient( ${color2}, ${color1} )`,
zIndex: "-1",
},
};
在原有背景的基础上,我增加了 before
伪元素,将它的位置大小与背景重叠,然后通过 backgroundImage
设置了背景的纹理:
backgroundImage: \`repeating-radial-gradient( circle at 0 0, transparent 0, #ffffff 9px ), repeating-linear-gradient( ${color2}, ${color1} )
再通过将原本的背景色设置为 transparent
透明,将 before
的层级设置为 -1
,将背后的纹理给透出去,实现的效果如下图:
好不好看这点仁者见仁,但是我觉得是精致了一点的。before
中的纹理是这样的:
是不是有点像阿尔卑斯糖呢 🍭。这个效果我是从一个背景生成网站中调整并生成的,网站地址在这:http://www.magicpattern.design/tools/css-b… ,这个网站提供了很多好看的背景纹理,可以在线调整颜色和间距,预览效果,还能直接复制 CSS 到代码里使用。
最后再放一下 26 个字母生成的头像效果,不同字母生成的颜色差别还是相对比较大的。我觉着效果都还不错,即便是不太好看的颜色在背景纹理和渐变的加成下也还凑合能看:
性能优化
最后我们看回前面生成颜色的函数,在代码里有这么一段用于生成两个渐变色的逻辑:
const color1 = getRandomColor(text, 1);
const color2 = getRandomColor(text, 0.7);
但是这里我们仅仅是改变了透明度,颜色其实是不变的,那么去计算两次颜色就没有必要了,我们可以先获取颜色,然后再改透明度,避免重复计算颜色。修改的方式有很多种,如果是你,你会怎么改呢?我的调整方式是这样的:
function getRandomColor(str: string) {
let asciiSum = 0;
for (let i = 0; i < str.length; i++) {
asciiSum += str.charCodeAt(i);
}
const red = Math.abs(Math.sin(asciiSum) * 256).toFixed(0);
const green = Math.abs(Math.sin(asciiSum + 1) * 256).toFixed(0);
const blue = Math.abs(Math.sin(asciiSum + 2) * 256).toFixed(0);
return (alpha: number) => `rgba(${red}, ${green}, ${blue}, ${alpha})`;
}
const color = getRandomColor(text);
const color1 = color(1);
const color2 = color(0.7);
这里我将函数进行柯里化,将一个多参数的函数转换为一系列单参数的函数,每个函数接受一个参数并返回一个函数,最终返回值由最后一个函数计算得出。
我们可以通过对 getRandomColor()
函数进行一次调用来获取一个特定字符串对应的颜色生成函数,然后多次调用该生成函数并传入不同的透明度参数来生成不同的颜色。这是柯里化的一个常见应用场景。
最终完整代码如下:
import { Avatar as ChakraAvatar, AvatarProps } from "@chakra-ui/react";
function getRandomColor(str: string) {
let asciiSum = 0;
for (let i = 0; i < str.length; i++) {
asciiSum += str.charCodeAt(i);
}
const red = Math.abs(Math.sin(asciiSum) * 256).toFixed(0);
const green = Math.abs(Math.sin(asciiSum + 1) * 256).toFixed(0);
const blue = Math.abs(Math.sin(asciiSum + 2) * 256).toFixed(0);
return (alpha: number) => `rgba(${red}, ${green}, ${blue}, ${alpha})`;
}
const getGradientStyle: (text: string) => AvatarProps = (text) => {
const color = getRandomColor(text);
const color1 = color(1);
const color2 = color(0.7);
return {
backgroundImage: `linear-gradient(135deg, ${color1}, ${color2})`,
color: "white",
display: "flex",
justifyContent: "center",
alignItems: "center",
fontWeight: "bold",
borderRadius: "10px",
background: "transparent",
textShadow: "1px 1px 3px rgba(0, 0, 0, 0.2)",
fontFamily: "Helvetica, Arial, sans-serif",
position: "relative",
_before: {
content: `""`,
position: "absolute",
left: "0",
top: "0",
w: "full",
h: "full",
borderRadius: "10px",
backgroundColor: "white",
backgroundImage: `repeating-radial-gradient( circle at 0 0, transparent 0, #ffffff 9px ), repeating-linear-gradient( ${color2}, ${color1} )`,
zIndex: "-1",
},
};
};
const Avatar: React.FC<{ name: string } & AvatarProps> = ({
name,
...rest
}) => {
return <ChakraAvatar {...getGradientStyle(name)} {...rest} name={name} />;
};
export default Avatar;
总结
后面我想着可以再将 纹理的类型和方向 也基于传入的字符串去定制,这样就能实现随机度更高的定制头像了,如果未来有了更好的效果我再单独写篇文章分享!
后续这类组件封装的文章可能会出一个系列,也准备把这些组件都开源了,如果有使用或打算使用 ChakraUI
进行项目搭建的同学欢迎插眼关注。如果文章对你有帮助除了收藏之余可以点个赞 👍,respect。
来源:juejin.cn/post/7218506966545170493