经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 职业生涯 » 查看文章
国庆微信头像DIY:轻松打造个性化头像
来源:cnblogs  作者:前端南玖  时间:2023/9/25 16:50:10  对本文有异议

前言

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

本文包含以下内容:

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

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

搭建项目,分析需求

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

  1. npm create vue@latest

为了方便,使用了Element PlusUI库

  1. npm install element-plus --save

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

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

  • 上传头像

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

  • 合成模版

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

  • 用户自定义内容

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

  • 合成头像

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

  • 下载合成后的头像

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

功能开发

上传头像

  1. <script setup lang="ts">
  2. // 用户头像
  3. const user_img = ref("");
  4. const change = (file: any, fileList: any) => {
  5. console.log(file, fileList);
  6. const fileReader = new FileReader();
  7. fileReader.readAsDataURL(file.raw);
  8. fileReader.onload = (e: any) => {
  9. user_img.value = e.target.result;
  10. };
  11. };
  12. // 删除用户头像
  13. const remove = () => {
  14. user_img.value = "";
  15. };
  16. </script>

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

合成模版

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

  1. <script>
  2. const gqList = ref([
  3. {
  4. id: 1,
  5. name: "模版1",
  6. img: getImg("gq0", "jpg"),
  7. template: getImg("tem1"),
  8. has: ["text"],
  9. textLabel: "请输入你的姓",
  10. desc: "最多输入1个字",
  11. text: "宋",
  12. textLength: 1,
  13. },
  14. {
  15. id: 2,
  16. name: "模版2",
  17. img: getImg("gq1", "jpg"),
  18. template: getImg("tem2"),
  19. },
  20. {
  21. id: 3,
  22. name: "模版3",
  23. img: getImg("gq2", "jpg"),
  24. template: getImg("tem3"),
  25. },
  26. {
  27. id: 4,
  28. name: "模版4",
  29. img: getImg("gq3", "jpg"),
  30. template: getImg("tem4"),
  31. has: ["text"],
  32. textLabel: "请输入祝福语",
  33. textColor: "#FED800",
  34. text: "生在国旗下,长在春风里",
  35. desc: "最多输入12个字, 请用中文逗号隔开",
  36. textLength: 12,
  37. },
  38. { id: 5, name: "模版5", img: getImg("gq4"), template: getImg("tem5") },
  39. {
  40. id: 6,
  41. name: "模版6",
  42. img: getImg("gq5", "jpg"),
  43. template: getImg("tem6"),
  44. has: ["text"],
  45. textLabel: "请输入祝福语",
  46. textColor: "#FED800",
  47. desc: "最多输入12个字, 请用中文逗号隔开",
  48. text: "不负韶华,只争朝夕",
  49. textLength: 12,
  50. },
  51. { id: 7, name: "模版7", img: getImg("gq6"), template: getImg("tem7") },
  52. ]);
  53. const template_id = ref(1);
  54. // 选择模版
  55. const gqChange = (val: any) => {
  56. console.log(val);
  57. template_id.value = val;
  58. generateImgRef.value.clear();
  59. generateImgRef.value.init();
  60. };
  61. </script>

合成图片

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

  1. // 模版4
  2. const drawImg4 = (ctx: any) => {
  3. const img = new Image();
  4. img.src = user_img.value;
  5. const gqImg = new Image();
  6. gqImg.src = gqList.value[template_id.value - 1].img;
  7. img.onload = () => {
  8. ctx.drawImage(img, 0, 0, 300, 300); // 绘制头像
  9. gqImg.onload = () => {
  10. ctx.drawImage(gqImg, 0, 0, 300, 300); // 绘制国庆图
  11. ctx.fillStyle = textColor.value; // 设置文字颜色
  12. ctx.font = "20px kaiti"; // 设置文字大小及字体
  13. const textList = text.value?.split(",") ?? []; // 以中文逗号分割文字
  14. textList.forEach((item: string, i: number) => {
  15. drawVerticalText(ctx, item ?? "", 20 + i * 20, 186 + i * 20, {
  16. size: 20,
  17. }); // 绘制文字
  18. });
  19. };
  20. canDownload.value = true; // 合成完成
  21. };
  22. };

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

  1. // 文字竖排
  2. const drawVerticalText = (
  3. context: any,
  4. text: string,
  5. x: number,
  6. y: number,
  7. font: any
  8. ) => {
  9. context.save();
  10. context.font = font;
  11. for (var i = 0; i < text.length; i++) {
  12. context.fillText(text[i], x, y + i * font.size);
  13. }
  14. context.restore();
  15. };

下载图片

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

  1. const downloadImg = () => {
  2. if (!canDownload.value) {
  3. ElMessage({
  4. message: "请先合成头像~",
  5. type: "warning",
  6. });
  7. return;
  8. }
  9. const url = canvas.value.toDataURL("image/png");
  10. const a = document.createElement("a");
  11. a.href = url;
  12. a.download = "国庆头像.png";
  13. a.click();
  14. };

自动化部署

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

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

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

体验

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

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

问题及解决方案

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

保存图片不清晰

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

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

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

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

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

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

移动端体验问题

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

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

  1. const downloadImg = () => {
  2. if (!canDownload.value) {
  3. ElMessage({
  4. message: "请先合成头像~",
  5. type: "warning",
  6. });
  7. return;
  8. }
  9. const url = canvas.value.toDataURL("image/png");
  10. if (devices.some((item) => ua.includes(item))) {
  11. ElMessageBox.alert(
  12. `
  13. 请长按图片保存
  14. <img src="${url}" style="width: 100%;height: 100%;object-fit: contain;" />
  15. `,
  16. "保存图片",
  17. {
  18. dangerouslyUseHTMLString: true,
  19. }
  20. );
  21. return;
  22. }
  23. const a = document.createElement("a");
  24. a.href = url;
  25. a.download = "国庆头像.png";
  26. a.click();
  27. };

打包部署问题

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

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

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

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

  1. const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g;
  2. const DRIVE_LETTER_REGEX = /^[a-z]:/i;
  3. build: {
  4. outDir: "dist",
  5. assetsDir: "assets",
  6. chunkSizeWarningLimit: 2000, // 解决包大小超过500kb的警告
  7. rollupOptions: {
  8. output: {
  9. manualChunks: {
  10. // elementPlus: ["element-plus"],
  11. // highlightjs: ["highlight.js"],
  12. },
  13. chunkFileNames: "assets/[name]-[hash].js",
  14. entryFileNames: "assets/[name]-[hash].js",
  15. assetFileNames: "assets/[name]-[hash].[ext]",
  16. sanitizeFileName: (name) => {
  17. const match = DRIVE_LETTER_REGEX.exec(name);
  18. const driveLetter = match ? match[0] : "";
  19. return (
  20. driveLetter +
  21. name.slice(driveLetter.length).replace(INVALID_CHAR_REGEX, "") // 处理文件名中的非法字符
  22. );
  23. },
  24. },
  25. },
  26. },

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

最后

整个内容到这里就结束了

原文链接:https://www.cnblogs.com/songyao666/p/17726739.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号