微信小程序踩坑记录 ------- canvas 生成带小程序码的微信朋友圈分享图

最近做了一个问卷类的小程序,其中的结果页想让用户进行朋友圈分享转发,网上搜索资料,得出解决思路,用 canvas 将页面绘制生成图片,然后保存到手机相册,最终效果如下:

这里写图片描述

在这里我只写页面里关于 canvas 生成图片并进行保存这个流程的相关代码,并且会在我踩过的坑那里进行具体的讲述。废话不多说直接上干货


wxml

<!-- 调用canvas图片绘制方法 按钮 -->
<view class="share" bindtap="share">
   <image class="share-img" src="/images/icon-share.png" mode="widthFix" lazy-load="true"></image>
   分享助力
</view>
<!-- 黑色透明层 -->
<view hidden="{{hiddenImg}}" class="mask"></view>
<!-- 调用保存图片到手机相册方法 按钮 -->
<view class="share-btn" hidden="{{hiddenImg}}" bindtap="save">保存到相册</view>
<!-- canvas -->
<view class='canvas-box'>
   <canvas canvas-id='share' style='width:100vw;height:100vh;' hidden='{{canvasHidden}}'></canvas>
</view>
<!-- canvas 绘制完成后显示的图片 -->
<image class="shareimg" src="{{shareImgPath}}" style="height:{{winHeight}}" mode="widthFix" hidden="{{hiddenImg}}"></image>

wxss

.share {
   width: 356rpx;
   height: 80rpx;
   text-align: center;
   line-height: 80rpx;
   color: #e73322;
   font-size: 32rpx;
   border-radius: 37rpx;
   background: #fff;
   margin: 70rpx auto 0;
}

.share-img {
   width: 31rpx;
}

.canvas-box {
   position: fixed;
   top: 99999px;
   left: 0;
   width: 100%;
}

.shareimg {
   position: fixed;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

.mask {
   position: fixed;
   z-index: 1;
   background: rgba(0, 0, 0, .5);
   top: 0;
   left: 0;
   bottom: 0;
   right: 0;
}

.share-btn {
   position: fixed;
   width: 356rpx;
   bottom: 50rpx;
   left: 197rpx;
   height: 80rpx;
   text-align: center;
   line-height: 80rpx;
   color: #e73322;
   font-size: 32rpx;
   border-radius: 37rpx;
   background: #fff;
   z-index: 999;
}

js

1. 设置 data 相关默认值

  • canvasHidden: true 设置使 canvas 隐藏,因为 canvas 是原生组件,拥有最高层级,如果不隐藏,会影响页面正常使用
  • hiddenImg: true 设置使黑色遮罩、保存按钮和显示生成后的图片隐藏
data: {
   canvasHidden: true, 
   wxappName: "来「 老字号文化影响力 」测试你的知识等级",
   hiddenImg: true
},

2. 在 onLoad 方法中准备需要用到的参数

这里需要注意一点我踩过的坑,

  • 由于小程序的canvas 不能使用网络图片,所以缓存中的头像不能直接用,需要使用wx.downloadFile方法将头像路径存储为临时路径,以供 canvas 使用,由于之前不知道这个,所有折腾半天,画出来的图片就是没有头像
  • 这里获取设备宽度、高度以及设备像素比也非常重要,用于最后显示图片的大小以及canvas画图过程中的宽高显示
onLoad: function (o) {
   var that = this;
   //读取缓存,获取微信头像和昵称
   wx.getStorage({
      key: 'user',
      success: function (res) {
         var nickName = res.data.nickName,
            avatarUrl = res.data.avatarUrl;
         that.setData({
            nickName: nickName,
         })
         // 由于canvas不能使用网络图片,所以此处进行头像临时路径存储
         wx.downloadFile({
            url: avatarUrl,
            success: (res) => {
               that.setData({
                  avatarUrl: res.tempFilePath,
               })
            },
         });
      }
   })
   //获取用户设备信息,屏幕宽度
   wx.getSystemInfo({
      success: res => {
         that.setData({
            screenWidth: res.screenWidth,
            winHeight: res.windowHeight,
            ratio: res.pixelRatio
         })
      }
   })

},

3. 编写 canvas 绘制方法

  • 第一步显示画板,配置需要显示的元素

这里需要注意 unit = that.data.screenWidth / 375 用于使用像素值进行绘制后进行手机不同机型大小的适配,context = wx.createCanvasContext('share') 用于指定要绘制的 canvas,其余的参数都是我项目中需要用到的,各位童鞋可以根据自己的需求进行配置

//定义的保存图片方法
share: function () {
   wx.showLoading({
      title: '图片生成中...',
   })
   var that = this;
   //设置画板显示,才能开始绘图
   that.setData({
      canvasHidden: false
   })
   var res = that.data.res.result;
   var resImg;
   switch (res) {
      case 1:
         resImg = '/images/sdj.png';
         break;
      case 2:
         resImg = '/images/js.png';
         break;
      case 3:
         resImg = '/images/jr.png';
         break;
      case 4:
         resImg = '/images/gs.png';
         break;
      case 5:
         resImg = '/images/xc.png';
         break;
   }
   var unit = that.data.screenWidth / 375;
   var ratio = that.data.ratio;
   var screenWidth = that.data.screenWidth;
   var winHeight = that.data.winHeight;
   var bg = "/images/bg.png"
   var avatarUrl = that.data.avatarUrl;
   var bgleavel = "/images/bg-leavel.png";
   var qrcode = "/images/qrcode.jpg";
   var nickName = that.data.nickName;
   var context = wx.createCanvasContext('share');
   var idnum = that.data.res.id;
   var num = idnum.toString();
   var length = num.length;
   var left;
   switch (length) {
      case 2:
         left = 375 - 208 - length * 26
         break;

      case 3:
         left = 375 - 208 - length * 24
         break;

      case 4:
         left = 375 - 208 - length * 20
         break;

      case 5:
         left = 375 - 208 - length * 19
         break;

      default:
         left = 375 - 208 - length * 18
         break;
   }
   var wxappName = that.data.wxappName;
  • 第二步开始绘制将所需元素逐一绘制到画板上

这里我有踩到的坑,就是图片绘制完成后,在手机上显示非常模糊,多番查找折腾后,发现以下几个地方设置好之后就OK了
width: screenWidth 设定指定的画布区域的宽度
height: winHeight 设定指定的画布区域的高度
destWidth: ratio * screenWidth 设定输出图片宽度
destHeight: ratio * winHeight 设定输出图片高度
quality: 1, 设定图片质量

destWidth 和 destHeight 需要设置为width 和height 的 2倍或以上才能让图片清晰,而现在的智能手机设备像素比一般都在2以上,所以这里直接用 ratio 来进行设置

图片绘制完成且临时路径生成之后,打开隐藏的遮罩层和保存按钮以及供用户浏览的生成之后的图片

   // 绘制红色背景
   context.drawImage(bg, 0, 0, that.data.screenWidth, winHeight)
   // 绘制头像
   var avatarurl_width = unit * 75; //绘制的头像宽度
   var avatarurl_heigth = unit * 75; //绘制的头像高度
   var avatarurl_x = unit * 150; //绘制的头像在画布上的位置
   var avatarurl_y = unit * 35; //绘制的头像在画布上的位置

   context.save();
   //先画个圆   前两个参数确定了圆心 (x,y) 坐标  第三个参数是圆的半径  四参数是绘图方向  默认是false,即顺时针
   context.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);

   context.clip(); //画好了圆 剪切  原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内 这也是我们要save上下文的原因

   context.drawImage(avatarUrl, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 将头像放到绘制好的圆中

   context.restore(); //恢复之前保存的绘图上下文状态 还可以继续绘制.

   // 绘制昵称
   context.setFontSize(14)
   context.setFillStyle('#fff')
   context.setTextAlign('center')
   context.fillText(nickName, unit * 187, unit * 135)
   // 绘制知识等级背景图
   context.drawImage(bgleavel, unit * 110, unit * 165, unit * 312 / 2, unit * 307 / 2)
   context.drawImage(resImg, unit * 160, unit * 180, unit * 110 / 2, unit * 238 / 2)
   // 绘制第几位宣传者
   context.setFontSize(16)
   context.setFillStyle('#fff')
   context.setTextAlign('right')
   context.fillText('你是第', left * unit, unit * 382)
   context.setFontSize(24)
   context.setFillStyle('#fff')
   context.setTextAlign('center')
   context.fillText(num, unit * (left + length * 12), unit * 382)
   context.setFontSize(16)
   context.setFillStyle('#fff')
   context.setTextAlign('left')
   context.fillText('位老字号文化传播大使', unit * (left + length * 24), unit * 382)

   // 绘制二维码
   context.drawImage(qrcode, unit * 138, unit * 410, unit * 204 / 2, unit * 204 / 2)
   // 绘制二维码下部文字
   context.setFontSize(12)
   context.setFillStyle("#fff")
   context.setTextAlign("center")
   context.fillText("长按识别小程序", unit * 187.5, unit * 540)
   context.fillText(wxappName, unit * 187.5, unit * 560)
   //把画板内容绘制成图片,并回调 画板图片路径
   context.draw(false, function () {
      wx.canvasToTempFilePath({
         x: 0,
         y: 0,
         width: screenWidth,
         height: winHeight,
         destWidth: ratio * screenWidth,
         destHeight: ratio * winHeight,
         canvasId: 'share',
         quality: 1,
         success: function (res) {
            that.setData({
               shareImgPath: res.tempFilePath,
               hiddenImg: false,
               pathRes: res
            })
            if (!res.tempFilePath) {
               wx.showModal({
                  title: '提示',
                  content: '图片绘制中,请稍后重试',
                  showCancel: false
               })
            }
            wx.hideLoading()
         }
      })
   });
},

4. 编写保存到手机相册方法

这里没什么好说的,就是调用微信提供的API 进行图片保存,成功或者失败之后,进行相应提示并将一开始在data中设置的需要默认隐藏的元素进行隐藏,然后就OK了,打开手机相册就可以看到完成的图片进行朋友圈分享了。

save: function () {
   var that = this;
   var res = that.data.pathRes;
   wx.saveImageToPhotosAlbum({
      filePath: res.tempFilePath,
      //保存成功失败之后,都要隐藏画板,否则影响界面显示。
      success: (res) => {
         wx.showToast({
            title: '保存成功',
            icon: 'none',
            duration: 1500,
            mask: false,
            success: function () {
               that.setData({
                  canvasHidden: true,
                  hiddenImg: true
               })
            }
         });
      },
      fail: (err) => {
         wx.showToast({
            title: '保存失败',
            icon: 'none',
            duration: 1500,
            mask: false,
            success: function () {
               that.setData({
                  canvasHidden: true,
                  hiddenImg: true
               })
            }
         });
      }
   })
}

最后写一下用到的API吧,常用的就不写了主要写一下我自己平时不怎么经常用到的

  1. wx.downloadFile 用于将网络图片生成临时路径,这里也有一个坑,需要在小程序公众平台将腾讯的 wx.qlogo.cn 这个域名设置为合法域名,否则会报错,在之后的绘制中图片尽量用本地路径
  2. wx.createCanvasContext 其中在画布中进行绘制的时候如果有什么不明白的,可以到 w3cschool 看看,链接是直接跳转到 canvas 绘图这一节的
  3. wx.canvasToTempFilePath 将画板内容绘制成图片的方法,需要注意上面提到的影响手机图片清晰度的参数,其中所有参数具体配置以及含义,可以直接到 官方文档 查看
  4. wx.saveImageToPhotosAlbum 保存图片到本地的API,这个没有难点,不多说了

整篇文章看着代码多,其实用到的不常见的API也就这几个,注意一下文章中我踩过的那几个坑,相信你可以开发出一个完美的带小程序码的用于分享到朋友圈的图片了。

========================================================================================================================

2020年4月16日 补充

1、字体加粗
通过文字多次绘制可以模拟字体的加粗
2、标题文字过长加省略号
通过 measureText 方法获取标题长度,与自己实际展示标题长度做判断,循环进行截取,直到满足条件为止

var titleWidth = context.measureText(title).width;
context.fontSize = 16 * unit;
if(titleWidth > (320*unit)){
    while (titleWidth > (320*unit)) {
        title = title.substring(0, (title.length - 1));
        titleWidth = context.measureText(title).width;
        // console.log(title,titleWidth,i,title.length,(title.length - 1))
    }
    title = title + '...';
}
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容