注册
web

想弄一个节日头像,结果全是广告!带你用 Canvas 自己制作节日头像

一、为什么要自己制作节日头像?

很多人想为节日换上特别的头像,尤其是在国庆这样的节日气氛中,给自己的WX头像添加节日元素成为了不少人的选择。最初我也以为只需通过一些WX公众号简单操作,就能轻松给头像加上节日图案,比如国庆节、圣诞节头像等。然而,实际体验却很糟糕——广告无处不在!每一步操作几乎都被强制插入广告打断,不仅浪费时间,体验也非常差。

为了避开这些广告,享受更自由、更个性化的制作过程,我决定分享一个不用看广告的好方法:使用 Canvas 自己动手制作一个专属的节日头像!

二、源码 & 在线体验

👀 在线体验 | 📖 源码地址 | 欢迎start、欢迎共同交流

注意事项

  • demo_admin 为体验用户,项目一人一号 ,如果体验人数过多,请自行选中项目中的登录方式进行登录
  • 本文源码在 yf/ yf-vue-admin / src / views / demo / festival-avatar

三、 实现的功能与后续发展

在解决了广告干扰的问题后,我通过 Canvas 实现了多个实用功能,让大家可以轻松制作个性化的节日头像:

  1. 头像裁剪功能
  2. 头像与框架的拼接
  3. 头像框透明度调节
  4. 头像框颜色过滤(可自定义头像框)
  5. 后续发展:Fabric.js 自定义贴图功能
  6. 后续发展:更新更多节日的头像 & 贴图

四、当前素材及投稿征集

展示目前头像框素材,也欢迎大家投稿,我也会陆续更进头像框(项目中头像框已进行分类,这里为了方便展示,也可以自定义头像框)

1. 头像框

image.png

2. 贴图

五、代码实现

整体逻辑非常简单 : 头像 + 头像框 = 所需头像

1. 头像裁剪功能

页面部分

  • 使用 :width 来根据设备类型设置宽度(device === DeviceEnum.MOBILE ? '95%' : '42%')。
  •  用于图像裁剪功能。
  • 底部有文件上传和旋转按钮。
<template>
<el-dialog
v-model="dialog.visible"
:width="device === DeviceEnum.MOBILE ? '95%' : '42%'"
class="festival-avatar-upload-dialog"
destroy-on-close
draggable
overflow
title="上传头像"
>

<div style="height: 45vh">
<vue-cropper
ref="cropper"
:autoCrop="true"
:centerBox="true"
:fixed="true"
:fixedNumber="[1,1]"
:img="imgTemp"
:outputType="'png'"
/>

div>
<template #footer>
<div class="festival-avatar-dialog-options">
<el-button @click="uploadAvatar">
<el-icon style="margin-right: 5px;">
<UploadFilled/>
el-icon>
上传头像
<input ref="avatarUploaderRef" accept="image/*" class="avatar-uploader" name="file" type="file"
@
change="handleFileChange">

el-button>
<el-button @click="rotateLeft">
<el-icon><RefreshLeft/>el-icon>
el-button>
<el-button @click="rotateRight">
<el-icon><RefreshRight/>el-icon>
el-button>
<el-button type="primary" @click="submitForm">提 交el-button>
div>
template>
el-dialog>
template>

代码逻辑部分(核心部分)

  • imgTemp 用来存储上传的临时图片数据。
  • handleFileChange 处理文件上传事件,校验文件类型并使用 FileReader 读取图片数据,并本地存储
  • rotateLeft 和 rotateRight 分别用于左旋和右旋图片。
// 省略部分属性定义
const imgTemp = ref<string>("") // 临时图片数据
const cropper = ref(); // 裁剪实例
const avatarUploaderRef = ref<HTMLInputElement | null>(null); // 上传头像 input 引用

// 上传头像功能
function uploadAvatar() {
avatarUploaderRef.value?.click(); // 点击 input 触发上传
}

// 上传文件前校验 : 略

// 处理文件上传
function handleFileChange(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files[0]) {
const file = input.files[0];

if (!beforeAvatarUpload(file)) return;

const reader = new FileReader();
reader.onload = (e: ProgressEvent) => {
imgTemp.value = e.target?.result as string; // 读取的图片数据赋给 imgTemp
};
reader.readAsDataURL(file);
}
}

// 旋转功能
function rotateLeft() {
cropper.value?.rotateLeft();
}

const rotateRight = () => {
cropper.value?.rotateRight();
};

实现效果图

image.png

2. 头像与头像框合并

页面部分 (核心部分)

  • compositeAvatar 为组合头像 , avatarData 为头像数据 ,compositeCanvas 头像 Canvas , avatarFrameCanvas 头像框 Canvas
  • 在没有 compositeAvatar 的时候展示 avatarData , 没有 avatarData 提示用户点击 PLUS 的图片


<div class="festival-avatar-preview">
<div class="festival-avatar-preview__plus" @click="openAvatarDialog">

<img v-if="compositeAvatar" :src="compositeAvatar" alt="合成头像"/>


<img v-else-if="avatarData" :src="avatarData" alt="头像"/>


<el-icon v-else color="#8c939d" size="28">
<Plus>Plus>
el-icon>
div>
div>



<canvas ref="compositeCanvas" style="display: none;">canvas>

<canvas ref="avatarFrameCanvas" style="display: none;">canvas>

逻辑部分 (核心部分)

  • 通过 toDataURL 转换后合成为组合头像 , 通过 drawImage 合并 avatarFrameCanvas 和上文中avatarData 进行合并
const context = getCanvasContext(compositeCanvas); // 获取主 Canvas 上下文
const frameContext = getCanvasContext(avatarFrameCanvas); // 获取头像框 Canvas 上下文

// 省略非相关逻辑 , context 中写入 avatarData 内容

// 将处理后的头像框绘制到主 Canvas 上
context.drawImage(avatarFrameCanvas.value, 0, 0, avatarImg.width, avatarImg.height);

// 将合成后的图片转换为数据 URL
compositeAvatar.value = compositeCanvas.value!.toDataURL('image/png');

实现效果

当我们点击头像框的时候,合并头像

QQ录屏20240928110300.gif

3. 头像框透明度调整

页面部分 与上文一样 , 通过调整 avatarFrameCanvas 的内容而调整头像框

逻辑部分 (核心部分)

通过 context  globalAlpha 属性设置全局透明度。

setFrameOpacity(frameContext, frameOpacity.value); // 设置头像框透明度

/**
* 设置 Canvas 的透明度
*
@param context Canvas 的 2D 上下文
*
@param opacity 透明度值
*/

function setFrameOpacity(context: CanvasRenderingContext2D, opacity: number) {
context.globalAlpha = opacity; // 设置全局透明度
}

实现效果

QQ录屏20240928110300.gif

4. 头像框颜色过滤

页面部分 与上文一样 , 通过调整 avatarFrameCanvas 的内容而调整头像框

服务对象 我们有自定义头像框功能,但是自己找的头像很容易有白底的问题,所以更新此功能。

逻辑部分 (核心部分)

filterColorToTransparent 函数

  • 作用:将与指定颜色相近的像素变为透明。

colorDistance 函数

  • 作用:计算两种颜色(RGB 值)之间的距离。距离越小,颜色越相似。
  • 计算方式:使用欧几里得距离公式计算两个 RGB 颜色向量之间的距离,如果距离小于一定的容差值(tolerance),则认为两种颜色足够接近。 image.png

rgbStringToArray 函数

  • 作用:将 RGB 字符串(例如 'rgb(255,255,255)')转换为包含 r, g, b 值的对象。
/**
* 将指定颜色过滤为透明
*
@param context Canvas 的 2D 上下文
*
@param width Canvas 宽度
*
@param height Canvas 高度
*/

function filterColorToTransparent(context: CanvasRenderingContext2D, width: number, height: number) {
const frameImageData = context.getImageData(0, 0, width, height);
const data = frameImageData.data;

const targetColor = rgbStringToArray(colorFilter.value.color); // 将目标颜色转换为 RGB 数组

// 遍历所有像素点
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const distance = colorDistance({r, g, b}, targetColor); // 计算当前颜色与目标颜色的差距

// 如果颜色差距在容差范围内,则将其透明度设为 0
if (distance <= colorFilter.value.tolerance) {
data[i + 3] = 0; // 设置 alpha 通道为 0(透明)
}
}

// 将处理后的图像数据放回 Canvas
context.putImageData(frameImageData, 0, 0);
}

/**
* 计算两种颜色之间的距离(欧几里得距离)
*
@param color1 颜色 1,包含 r、g、b 属性
*
@param color2 颜色 2,包含 r、g、b 属性
*
@returns number 返回颜色之间的距离
*/

function colorDistance(color1: { r: number; g: number; b: number }, color2: { r: number; g: number; b: number }) {
return Math.sqrt(
(color1.r - color2.r) ** 2 +
(color1.g - color2.g) ** 2 +
(color1.b - color2.b) ** 2
);
}

/**
* 将 RGB 字符串转换为 RGB 数组
*
@param rgbString RGB 字符串(例如 'rgb(255,255,255)')
*
@returns 返回一个包含 r、g、b 值的对象
*/

function rgbStringToArray(rgbString: string) {
const result = rgbString.match(/\d+/g)?.map(Number) || [0, 0, 0]; // 匹配并转换 RGB 值
return {r: result[0], g: result[1], b: result[2]}; // 返回 r、g、b 对象
}

实现效果

  1. 在 Canva 自己制作一个头像

image.png

  1. 上传头像框,制作头像 ( 过滤白色 )

QQ图片20240707160518.gif

六、结束语

开发很容易,祝大家各个节日快乐 !!!


作者:翼飞
来源:juejin.cn/post/7419223935005605914

0 个评论

要回复文章请先登录注册