微信小程序纯前端生成海报并保存本地

需求

公司开发微信小程序,有一个海报页面,需要用户点击生成海报,可以将该该swipe-item 生成一个带二维码的图片,最终由纯前端实现!
这里写图片描述

技术调研

  • 因为小程序的打包限制,不可能将所有的图片都放在代码目录里面。所以就得使用远端的图片,但是因为canvas对跨域图片的不支持,可以使用微信小程序中的API :wx.getImageInfo()现将远端的图片临时保存。
  • canvas 组件是由客户端创建的原生组件,它的层级是最高的,不能通过 z-index 控制层级;并且使用display:none 和 visibility:hidden 的方式隐藏会导致canvas无法捕获画布内容,所以通过给canvas外部嵌套一个width:0;height:0;overflow:hidden的父级。

实现步骤

将远端图片暂存本地(以用户头像为例)

wx.getUserInfo({    //获取微信用户信息
      success: function (res) {
        this.getImageInfo(res.userInfo.avatarUrl);  //  调取图片处理方法
        this.setData({
          userName: res.userInfo.nickName
        });
      }
    });
getImageInfo(url){    //  图片缓存本地的方法
    if(typeof url === 'string'){
      wx.getImageInfo({   //  小程序获取图片信息API
        src: url,
        success: function (res) {
         this.setData({
           head_img:res.path
         })
        },
        fail(err) {
          console.log(err)
        }
      })
    }

canvas绘制图片

点击生成海报按钮
handlePoster(e){
    wx.getSetting({  // 获取用户设置
      success(res) {
        if (!res.authSetting['scope.writePhotosAlbum']) {  // 如果用户之前拒绝了授权
           wx.openSetting({
            success(tag){
              if (tag.authSetting["scope.writePhotosAlbum"]){  // 用户在设置页选择同意授权
                // wx.showLoading({
                //   title: '正在生成...',
                // })
              }
            }
          });
        }else{   //  用户已经授权
          wx.showLoading({
            title: '正在生成...',
          })
        }
      }
    })

此时页面出现loading状态,canvas开始绘制海报:

首先绘制整个底部背景

在这里,我暂且用本地图片作为演示代替。绘制的canvas图片大小为750px*1344px大小,最终生成格式为jpg。大小大概就200多kb。非常的小,清晰度也不错。
这里写图片描述
我选择的背景图如上图所示;

    const ctx = wx.createCanvasContext('notes');
    ctx.clearRect(0, 0, 0, 0); 
    const arr2 = ['./big_img1.png', this.data.nodesData[index].detail_pic];    // 有图片海报背景图&&海报正文图片
    const WIDTH=750;
    const HEIGHT=1334;
    //  绘制图片模板的 底图
      ctx.drawImage(arr2[0], 0, 0, WIDTH, HEIGHT);
绘制海报的正文图片
//  绘制图片模板的 banner图
      ctx.drawImage(arr2[1], 40,40, 670, 580);
绘制时间

因为我们的海报上面的时间 xxxx.xx.xx中的每个数字和点都是图片,所以时间也是一个个的小的以数字命名的图片绘制上去。而后台提供给我们的时间是个 秒(s)为单位的时间戳。首先得将时间戳转换为一个时间数字组成的数组:

dateFilter(date){
    let gmt = new Date(date * 1000);
    let nYear=String(gmt.getFullYear());
    let nMonth =this.numFormatting(gmt.getMonth()+1);
    let nDay =this.numFormatting(gmt.getDate());
    let end_date = (nYear + '.' + nMonth + '.' + nDay).split("") ;
    return end_date
  },
   /**
   *  处理 月份 天数 个位数
   */
  numFormatting(num){
    if(String(num).length<2){
      return '0'+num
    }else{
      return num
    }
  },

假设处理好的时间格式为 :

let times=['2', '0', '1', '8', '.', '0', '4','.','1','2'];

接下来是绘制时间:

 //  绘制 图片模板 的时间
      const TEXT_DATE = ['2', '0', '1', '8', '.', '0', '4','.','1','2'];
      for (let i = 0; i < TEXT_DATE.length; i++) {
        if (TEXT_DATE[i] != '.') {
          var path3 = `./${TEXT_DATE[i]}.png`;
        } else {
          var path3 = './point.png';
        }
        let clientx = 40 + 16 * i;
        ctx.drawImage(path3, clientx, 640, 16, 32);
      }
绘制用户头像

因为二维码中显示的用户头像是一个圆形图像,所以处理方式为:

// 绘制头像
    ctx.save();
    let r=32;
    let d = r*2;
    let cx = 102;
    let cy = 1172;
    ctx.arc(cx+r, cy+r, r, 0, 2 * Math.PI);
    ctx.clip();
    ctx.drawImage(this.data.head_img, cx, cy, d, d);
    ctx.restore();

这个时候 ,海报所需要的所有图片已经绘制成功,接下来我们开始处理文字部分:

canvas 绘制文字

canvas并不会自动的将文字折行处理,所以我们得自己考虑每行显示多少个文字,还得考虑中英文和符号所占字节的不同,在此,封装一个处理方法:

textByteLength(text,num){  // text为传入的文本  num为单行显示的字节长度
    let strLength = 0; // text byte length
    let rows=1;
    let str=0;
    let arr=[];
    for (let j = 0; j < text.length; j++) {
          if (text.charCodeAt(j) > 255) {
            strLength += 2;
            if (strLength > rows * num) {
              strLength++;
              arr.push(text.slice(str, j));
              str = j;
              rows++;
            }
          } else {
            strLength++;
            if (strLength > rows * num) {
              arr.push(text.slice(str, j));
              str = j;
              rows++;
            }
          }
      }
    arr.push(text.slice(str, text.length));
    return [strLength, arr, rows]   //  [处理文字的总字节长度,每行显示内容的数组,行数]
  },

这个时候 就可以开始绘制文字了:

绘制正文和 出处
const CONTENT_ROW_LENGTH = 40;  // 正文 单行显示字符长度
      let [contentLeng, contentArray, contentRows] = this.textByteLength(this.data.nodesData[index].text, CONTENT_ROW_LENGTH);
      ctx.setTextAlign('left')
      ctx.setFontSize(32);
      let contentHh = 32 * 1.3;
      for (let m = 0; m < contentArray.length; m++) {
        ctx.fillText(contentArray[m], 40, 732 + contentHh * m);
      }
      //  绘制 出处
      ctx.setTextAlign('right')
      ctx.setFontSize(32);
      ctx.fillText(`——${this.data.nodesData[index].refer}`, 710, 996, 710);
绘制二维码边上的说明
//  绘制二维码右边说明
    ctx.setTextAlign('left')
    ctx.setFontSize(28);
    ctx.setFillStyle('rgba(34,34,34,.64)')
    ctx.fillText('长按小程序码', 250, 1174);
    ctx.fillText(`${this.data.userName}邀你进入掌阅读好书`, 250, 1230);
    ctx.draw();

此时如果canvas标签能够显示查看,就能看到一个完美的海报绘制完成,现在只差最后一步,保存canvas;使用微信方法 canvasToTempFilePath()将canvas海报保存到本地临时文件路径;在使用saveImageToPhotosAlbum()将图片保存到本地相册:

setTimeout(function () {
      wx.canvasToTempFilePath({
        canvasId: 'notes',
        fileType:'jpg',
        success: function (res) {
          wx.saveImageToPhotosAlbum({
            filePath: res.tempFilePath,
            success(res) {
              wx.hideLoading();
              wx.showToast({
                title: '保存成功',
              });
            },
            fail(){
              wx.hideLoading()
            }
          })
        }
      })
    }, 500);

然后查看存储图片的路径文件夹,最终生成的海报:

这里写图片描述

总结:

在做这个功能之前,canvas用的非常少,很多方法都是现成百度查询得出来的结果,这次的canvas生成海报并保存本地的实现过程中,让我学到了很多东西;因为项目做得比较急,很多的代码没有经过细致的打磨,在此,也希望看到本文章的朋友们及时指点。

  • 13
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值