当前位置:   article > 正文

Uniapp微信小程序使用canvas绘制并下载图片_uniapp canvas

uniapp canvas

背景

最近项目中有一个下载图片和一个分享海报的功能,遇到了一些问题,比如海报图片生成失败、图片下载是空白的、多张图片下载时下载的还是前一张图片等等。觉得过程挺有意思的,记录一下。

创建容器及样式

  1. <view class="canvas-container">
  2. <canvas
  3. :style="{ width: canvasW + 'px', height: canvasH + 'px' }"
  4. canvas-id="myCanvas"
  5. id="myCanvas01"
  6. >
  7. </canvas>
  8. </view>
  9. <view class="convas-image">
  10. <canvas :style="{ width: canvasW + 'px',height: canvasH + 'px'}" canvasid="myCanvas2" id="myCanvas02">
  11. </canvas>
  12. </view>

由于要做移动端适配,故容器的宽高是动态的。

  1. .canvas-container {
  2. position: fixed;
  3. top: 9999999px;
  4. #myCanvas01 {
  5. background: rgba(255, 255, 255, 0); /*关键点*/
  6. }
  7. }
  8. .convas-image {
  9. position: fixed;
  10. top: 9999999px;
  11. #myCanvas02 {
  12. background: rgba(255, 255, 255, 0); /*关键点*/
  13. }
  14. }

因为这两个容器实际上都是不显示在页面上的,如果使用hidden或者v-if隐藏的话多多少少都会有些问题,所以使用固定定位使容器脱离文档流。

准备工作

  1. // 获取设备信息
  2. getSystemInfo() {
  3. return new Promise((req, rej) => {
  4. uni.getSystemInfo({
  5. success: function (res) {
  6. req(res);
  7. },
  8. });
  9. });
  10. },
  1. getImageInfo(image) {
  2. console.log(`output->image`, image);
  3. return new Promise((req, rej) => {
  4. uni.getImageInfo({
  5. src: image,
  6. success: function (res) {
  7. req(res);
  8. },
  9. fail: (err) => {
  10. uni.showToast({
  11. icon: "error",
  12. mask: true,
  13. title: "获取照片失败",
  14. });
  15. console.log("****", err);
  16. },
  17. });
  18. });
  19. },

获取设备信息及辅助函数,因为canvas中使用的图片为网络方式返回,此函数确保图片被正常读取后再绘制,后文有用到。

海报的绘制

  1. async canvas2dFun(retryCount = 3) {
  2. this.SystemInfo = await this.getSystemInfo();
  3. const w = this.SystemInfo.windowWidth / 750;
  4. let goodsImgPath = await this.getImageInfo(this.imgObj.urls[0]);//海报主图
  5. let ewmImgPath = await this.getImageInfo(this.imgObj.qrCode);//海报二维码
  6. let posterPath = await this.getImageInfo(
  7. `${this.imgpath}/static_pro/create/poster.png`
  8. );//海报边框
  9. let logoPath = await this.getImageInfo(
  10. `${this.imgpath}/static_pro/create/logo.png`
  11. );//logo
  12. this.canvasW = 710 * w;
  13. this.canvasH = 1124 * w;
  14. if (this.SystemInfo.errMsg == "getSystemInfo:ok") {
  15. console.log("读取图片信息成功");
  16. var ctx = uni.createCanvasContext("myCanvas", this);
  17. // 1.填充背景色,白色
  18. // 设置画布透明度
  19. ctx.setFillStyle("rgba(255, 255, 255, 1)"); // 默认白色
  20. ctx.fillRect(0, 0, this.canvasW, this.canvasH); // fillRect(x,y,宽度,高度)
  21. ctx.drawImage(
  22. goodsImgPath.path,
  23. 5 * w,
  24. 5 * w,
  25. this.canvasW - 15 * w,
  26. this.canvasH - 15 * w
  27. ); // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度)
  28. ctx.drawImage(posterPath.path, 0, 0, this.canvasW, this.canvasH); // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度,二维码的宽,高)
  29. ctx.drawImage(
  30. logoPath.path,
  31. this.canvasW - this.ewmW * w - 460 * w,
  32. this.canvasH - this.ewmW * w - 100 * w,
  33. this.logoW * w - 30 * w,
  34. this.logoH * w - 30 * w
  35. ); //logo
  36. ctx.drawImage(
  37. ewmImgPath.path,
  38. this.canvasW - this.ewmW * w - 460 * w,
  39. this.canvasH - this.ewmW * w - 16 * w,
  40. this.ewmW * w - 30 * w,
  41. this.ewmW * w - 30 * w
  42. ); // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度,二维码的宽,高)
  43. ctx.font = "bold 18px Arial";
  44. ctx.fillStyle = "#222222";
  45. ctx.fillText(
  46. "微信扫码 XX相机",
  47. this.canvasW - this.ewmW * w - 280 * w,
  48. this.canvasH - this.ewmW * w + 50 * w
  49. );
  50. ctx.font = "normal 12px Arial";
  51. ctx.fillText(
  52. "这是我的写真,你也来试一试吧",
  53. this.canvasW - this.ewmW * w - 280 * w,
  54. this.canvasH - this.ewmW * w + 100 * w
  55. );
  56. // draw方法 把以上内容画到 canvas 中
  57. ctx.draw(true, (ret) => {
  58. uni.canvasToTempFilePath(
  59. {
  60. // 保存canvas为图片
  61. canvasId: "myCanvas",
  62. quality: 1,
  63. success: (res) => {
  64. console.log("生成海报-》", res);
  65. uni.setStorageSync("filePath", res.tempFilePath); // 保存临时文件路径到缓存
  66. },
  67. fail: (error) => {
  68. console.log("生成海报失败-》", error);
  69. if (retryCount > 0) {
  70. console.log(`重试剩余次数: ${retryCount - 1}`);
  71. this.canvas2dFun(retryCount - 1)
  72. }
  73. },
  74. },
  75. this
  76. );
  77. });
  78. } else {
  79. console.log("读取图片信息失败");
  80. }
  81. },

绘制完成之后将绘制的图片存入filePath临时路径中,方便后面下载等操作。

测试阶段发现,部分安卓机型在canvas第一次绘制时会报wx.canvasToTempFilePath:create bitmap failed,可能是不分机型的性能使得图形绘制失败,图片保存下来也是纯白色的底图。在这里fail回调处理了如果生成失败再生成一次的操作。

  1. shareFirendCircle() {
  2. let posterUrl = uni.getStorageSync("filePath");
  3. uni.showShareImageMenu({
  4. path: posterUrl, //图片地址必须为本地路径或者临时路径
  5. success: (re) => {
  6. console.log({ success: re });
  7. },
  8. fail: (re) => {
  9. console.log({ fail: re });
  10. },
  11. });
  12. },

页面按钮点击后调用函数唤起分享框,函数中的posterUrl为canvas绘制的图片。

图片的下载

  1. async canvas2dImage(retryCount = 3) {
  2. this.SystemInfo = await this.getSystemInfo();
  3. const w = this.SystemInfo.windowWidth / 750;
  4. let goodsImgPath = await this.getImageInfo(this.imgObj.urls[this.swiperIndex]);
  5. let logoPath = await this.getImageInfo(
  6. `${this.imgpath}/static_pro/create/logo.png`
  7. );
  8. this.canvasW = 710 * w;
  9. this.canvasH = 1124 * w;
  10. if (this.SystemInfo.errMsg == "getSystemInfo:ok") {
  11. console.log("读取图片信息成功");
  12. var ctx = uni.createCanvasContext("myCanvas2", this);
  13. // 1.填充背景色,白色
  14. // 设置画布透明度
  15. ctx.setFillStyle("rgba(255, 255, 255, 1)"); // 默认白色
  16. ctx.fillRect(0, 0, this.canvasW, this.canvasH); // fillRect(x,y,宽度,高度)
  17. ctx.drawImage(
  18. goodsImgPath.path,
  19. 5 * w,
  20. 5 * w,
  21. this.canvasW - 15 * w,
  22. this.canvasH - 15 * w
  23. ); // drawImage(图片路径,x,y,绘制图像的宽度,绘制图像的高度)
  24. ctx.drawImage(
  25. logoPath.path,
  26. this.canvasW - this.ewmW * w - 480 * w,
  27. this.canvasH - this.ewmW * w + 100 * w,
  28. this.logoW * w - 30 * w,
  29. this.logoH * w - 30 * w
  30. ); //logo
  31. // draw方法 把以上内容画到 canvas 中
  32. return new Promise((resolve, reject) => {
  33. ctx.draw(false, (ret) => {
  34. uni.canvasToTempFilePath(
  35. {
  36. // 保存canvas为图片
  37. canvasId: "myCanvas2",
  38. quality: 1,
  39. success: (res) => {
  40. console.log("生成高清图-》", res);
  41. uni.setStorageSync("ImagePath", res.tempFilePath); // 保存临时文件路径到缓存
  42. resolve();
  43. },
  44. fail: (error) => {
  45. console.log("生成高清图失败-》", error);
  46. if (retryCount > 0) {
  47. console.log(`重试剩余次数: ${retryCount - 1}`);
  48. this.canvas2dImage(retryCount - 1)
  49. .then(resolve)
  50. .catch(reject);
  51. } else {
  52. reject(error);
  53. }
  54. },
  55. },
  56. this
  57. );
  58. });
  59. });
  60. } else {
  61. console.log("读取图片信息失败");
  62. }
  63. },

这里使用Promise的原因是有多张图片需要单独下载,切换图片点击下载按钮开始绘制图像,确保在图像canvas生成完毕后继续下载的步骤。

canvas绘制方面与上面大差不差,主要是下载方面需要单独配置

  1. async dowloadImg() {
  2. if (!this.$utils.getUserInfo().isVip) {
  3. //无会员
  4. this.openPlusAlert = true;
  5. } else {
  6. uni.showLoading({
  7. title: "照片保存中",
  8. });
  9. await this.canvas2dImage();
  10. let that = this;
  11. // 获取用户是否开启 授权保存图片到相册。
  12. uni.getSetting({
  13. success(res) {
  14. console.log("已知权限", res);
  15. uni.hideLoading();
  16. // 如果没有授权
  17. if (!res.authSetting["scope.writePhotosAlbum"]) {
  18. // 则拉起授权窗口
  19. uni.authorize({
  20. scope: "scope.writePhotosAlbum",
  21. success() {
  22. that.saveImage();
  23. },
  24. fail(error) {
  25. //点击了拒绝授权后--就一直会进入失败回调函数--此时就可以在这里重新拉起授权窗口
  26. console.log("拒绝授权则拉起弹框", error);
  27. uni.showModal({
  28. title: "提示",
  29. content: "若点击不授权,将无法保存图片",
  30. cancelText: "不授权",
  31. cancelColor: "#999",
  32. confirmText: "授权",
  33. confirmColor: "#f94218",
  34. success(res) {
  35. console.log(res);
  36. if (res.confirm) {
  37. // 选择弹框内授权
  38. uni.openSetting({
  39. success(res) {
  40. console.log(res.authSetting);
  41. },
  42. });
  43. } else if (res.cancel) {
  44. // 选择弹框内 不授权
  45. console.log("用户点击不授权");
  46. }
  47. },
  48. });
  49. },
  50. });
  51. } else {
  52. // 有权限--直接保存
  53. console.log("有权限 直接调用相应方法");
  54. uni.hideLoading();
  55. that.saveImage();
  56. }
  57. },
  58. fail: (error) => {
  59. console.log(
  60. "调用微信的查取权限接口失败,并不知道有无权限!只有success调用成功才只知道有无权限",
  61. error
  62. );
  63. uni.hideLoading();
  64. uni.showToast({
  65. title: error.errMsg,
  66. icon: "none",
  67. duration: 1500,
  68. });
  69. },
  70. });
  71. }
  72. },

这里是点击下载绑定的函数,主要作用是判断是否开启了权限。

  1. saveImage() {
  2. let filePath = uni.getStorageSync("ImagePath"); //从缓存中读取临时文件路径
  3. wx.saveImageToPhotosAlbum({
  4. filePath: filePath,
  5. success(res) {
  6. uni.showToast({
  7. icon: "success",
  8. mask: true,
  9. title: "保存到相册了",
  10. });
  11. },
  12. fail(res) {
  13. console.log(res.errMsg);
  14. },
  15. });
  16. },

保存图片到本地的操作。中心思想还是把canvas生成的临时路径下载保存到本地。

小结

这样就完成了上面轮播图切换时下载对应的图片,以及海报的分享与下载功能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/964339
推荐阅读
相关标签
  

闽ICP备14008679号