国庆微信头像DIY:轻松打造个性化头像

前言

国庆节马上要到了,今天就教你如何从0到1使用canvas生成国庆风微信头像。

本文包含以下内容:

  • vue3项目搭建,需求分析
  • canvas合成图片原理
  • github自动化部署
  • 开发过程遇到的问题及解决方案

文末附源码及在线体验地址~

搭建项目,分析需求

项目的话就直接使用脚手架生成一个 Vue3 + TS项目

shell 复制代码
npm create vue@latest

为了方便,使用了Element PlusUI库

shell 复制代码
npm install element-plus --save

配置的话,可以查看文档,全局导入、按需导入都可以看自己的需求

项目搭建完后,就可以来分析一下本次需求大概会涉及哪些功能了

  • 上传头像

这是一个合成微信头像的工具,那就必须得让用户上传自己的微信头像了

  • 合成模版

为了方便,我们当然还需要提供多种模版供用户自己选择

  • 用户自定义内容

为了让生成的头像更具独一无二性,我们还需要提供用户自定义内容的功能,比如:用户输入文字、选择文字颜色等

  • 合成头像

本次需求的重点当然是合成头像了

  • 下载合成后的头像

用户合成完当然还得支持让他下载

功能开发

上传头像

vue 复制代码
<script setup lang="ts">
// 用户头像
const user_img = ref("");
const change = (file: any, fileList: any) => {
  console.log(file, fileList);
  const fileReader = new FileReader();
  fileReader.readAsDataURL(file.raw);
  fileReader.onload = (e: any) => {
    user_img.value = e.target.result;
  };
};

// 删除用户头像
const remove = () => {
  user_img.value = "";
};
</script>

这部分比较简单,主要是用户上传自己的微信头像后再进行展示,UI部分就不贴了,后面有源码。

合成模版

合成模版部分,这里主要是需要考虑各个模版所需要的合成功能有哪些

vue 复制代码
<script>
const gqList = ref([
  {
    id: 1,
    name: "模版1",
    img: getImg("gq0", "jpg"),
    template: getImg("tem1"),
    has: ["text"],
    textLabel: "请输入你的姓",
    desc: "最多输入1个字",
    text: "宋",
    textLength: 1,
  },
  {
    id: 2,
    name: "模版2",
    img: getImg("gq1", "jpg"),
    template: getImg("tem2"),
  },
  {
    id: 3,
    name: "模版3",
    img: getImg("gq2", "jpg"),
    template: getImg("tem3"),
  },
  {
    id: 4,
    name: "模版4",
    img: getImg("gq3", "jpg"),
    template: getImg("tem4"),
    has: ["text"],
    textLabel: "请输入祝福语",
    textColor: "#FED800",
    text: "生在国旗下,长在春风里",
    desc: "最多输入12个字, 请用中文逗号隔开",
    textLength: 12,
  },
  { id: 5, name: "模版5", img: getImg("gq4"), template: getImg("tem5") },
  {
    id: 6,
    name: "模版6",
    img: getImg("gq5", "jpg"),
    template: getImg("tem6"),
    has: ["text"],
    textLabel: "请输入祝福语",
    textColor: "#FED800",
    desc: "最多输入12个字, 请用中文逗号隔开",
    text: "不负韶华,只争朝夕",
    textLength: 12,
  },
  { id: 7, name: "模版7", img: getImg("gq6"), template: getImg("tem7") },
]);
const template_id = ref(1);
// 选择模版
const gqChange = (val: any) => {
  console.log(val);
  template_id.value = val;
  generateImgRef.value.clear();
  generateImgRef.value.init();
};
</script>

合成图片

这里其实也不难,主要是使用canvas来绘制图片以及文字,由于各个模版的合成逻辑不一样,这里就不全部展示了,但整体上的合成流程是一样

js 复制代码
// 模版4
const drawImg4 = (ctx: any) => {
  const img = new Image();
  img.src = user_img.value;
  const gqImg = new Image();
  gqImg.src = gqList.value[template_id.value - 1].img;
  img.onload = () => {
    ctx.drawImage(img, 0, 0, 300, 300); // 绘制头像
    gqImg.onload = () => {
      ctx.drawImage(gqImg, 0, 0, 300, 300); // 绘制国庆图
      ctx.fillStyle = textColor.value; // 设置文字颜色
      ctx.font = "20px kaiti"; // 设置文字大小及字体
      const textList = text.value?.split(",") ?? []; // 以中文逗号分割文字
      textList.forEach((item: string, i: number) => {
        drawVerticalText(ctx, item ?? "", 20 + i * 20, 186 + i * 20, {
          size: 20,
        }); // 绘制文字
      });
    };
    canDownload.value = true; // 合成完成
  };
};

这里主要的难点在于canvas默认不支持文字竖排绘制,所以这里需要特殊处理,原理其实就是遍历文字,计算文字高度,然后再一个一个去绘制

js 复制代码
// 文字竖排
const drawVerticalText = (
  context: any,
  text: string,
  x: number,
  y: number,
  font: any
) => {
  context.save();
  context.font = font;
  for (var i = 0; i < text.length; i++) {
    context.fillText(text[i], x, y + i * font.size);
  }
  context.restore();
};

下载图片

这里主要是借助a标签的download属性,这里在手机上有点坑,后面会提到...

js 复制代码
const downloadImg = () => {
  if (!canDownload.value) {
    ElMessage({
      message: "请先合成头像~",
      type: "warning",
    });
    return;
  }

  const url = canvas.value.toDataURL("image/png");
  const a = document.createElement("a");
  a.href = url;
  a.download = "国庆头像.png";
  a.click();
};

自动化部署

这里其实之前有写过文章,主要是使用github action来完成

搭建完就是这样的,我们写完代码只需要将代码提交上去就能够自动打包部署了

对这个不了解的可以去看我之前的文章:使用GitHub Actions实现自动化部署

体验

开发部署完就可以来体验一下了:体验地址

PC上体验下来,效果还可以。

问题及解决方案

开发过程中也遇到一些问题,来看看是如何解决的吧

保存图片不清晰

canvas绘制图片不清晰的原因主要是:

  • 图片被放大或缩小
  • 图片没处于完整像素的位置

因为canvas是点阵图,由一个个像素组成,当图像被放大时,一个像素会被强形拉伸至一个以上,多出来的像素均匀的分部在图像中,计算机为了使拉伸后的图像看起来平滑,会给这些多出来的像素计算出一个过渡色,缩小图像时,多个像素合成一个像素,计算机会用这多个像素的色彩值计算出一个过渡色来填充这个像素,不管是放大还是缩小,都会造成图像原有像素信息丢失。

所以只需要加上以下代码就能解决

js 复制代码
const dpr = window.devicePixelRatio || 1; // 获取设备的devicePixelRatio
canvas.value.width = 300 * dpr; // 画布宽高放大dpr倍,绘制后再缩小dpr倍,解决模糊问题
canvas.value.height = 300 * dpr; // 画布宽高放大dpr倍,绘制后再缩小dpr倍,解决模糊问题
canvas.value.style.width = "300px"; // 显示高
canvas.value.style.height = "300px"; // 显示高
ctx.value.scale(dpr, dpr); // 按比例缩放画布,解决模糊问题

优化完,清晰度提升还是非常明显的

移动端体验问题

手机上下载图片会失败,这主要是因为blob格式在手机上不能下载,base64格式有点大,那就只能上传CDN再进行下载了?

不需要,我们可以利用手机上的长按图片保存来实现

js 复制代码
const downloadImg = () => {
  if (!canDownload.value) {
    ElMessage({
      message: "请先合成头像~",
      type: "warning",
    });
    return;
  }

  const url = canvas.value.toDataURL("image/png");
  if (devices.some((item) => ua.includes(item))) {
    ElMessageBox.alert(
      `
    请长按图片保存
    <img src="${url}" style="width: 100%;height: 100%;object-fit: contain;" />
    `,
      "保存图片",
      {
        dangerouslyUseHTMLString: true,
      }
    );
    return;
  }
  const a = document.createElement("a");
  a.href = url;
  a.download = "国庆头像.png";
  a.click();
};

打包部署问题

打包生成的_plugin-vue_export-helper.cdc0426e.js文件访问404,刚开始我还以为是打包路径配置的有问题,但如果是打包路径的问题的话也不会只有这一个文件有问题。

最终,我在viteissues中找到了答案

简单点讲就是Github Pages 阻止了以下划线字符开头的文件,所以会导致这个文件访问返回404.

解决方法就是修改打包逻辑:

js 复制代码
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g;
const DRIVE_LETTER_REGEX = /^[a-z]:/i;

build: {
    outDir: "dist",
    assetsDir: "assets",
    chunkSizeWarningLimit: 2000, // 解决包大小超过500kb的警告
    rollupOptions: {
      output: {
        manualChunks: {
          // elementPlus: ["element-plus"],
          // highlightjs: ["highlight.js"],
        },
        chunkFileNames: "assets/[name]-[hash].js",
        entryFileNames: "assets/[name]-[hash].js",
        assetFileNames: "assets/[name]-[hash].[ext]",
        sanitizeFileName: (name) => {
          const match = DRIVE_LETTER_REGEX.exec(name);
          const driveLetter = match ? match[0] : "";
          return (
            driveLetter +
            name.slice(driveLetter.length).replace(INVALID_CHAR_REGEX, "") // 处理文件名中的非法字符
          );
        },
      },
    },
  },

vite.config.ts中加上以上代码,重新提交部署就可以了。

最后

整个内容到这里就结束了

相关推荐
y先森16 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy16 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891119 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端