微信小程序 canvas 生成图片保存本地 并分享至朋友圈

微信小程序canvas生成图片

在生成图片时遇到以下问题:

  1. canvas绘制文字不能换行,如果文字的长度大于你所定的宽度的话,文字会超出你所定宽度,或者文字会挤压变形

解决方法:
(1)截取一定的文字长度,多余的文字省略
(2)通过技术来使文字换行,我们的需求就是 文字换行。

  1. 微信小程序canvas画图必须用本地的图片路径,所以要把图片下载下来存为临时路径再绘制图片。微信提供了个api可用:wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。

这就涉及到一个问题:封面图是从自己的服务器请求的,所以可以下载并得到临时路径,但是用户的头像的域名地址是腾讯的,而微信公众平台的域名配置是不可能配置别人的域名的,所以是获取不到用户头像的临时路径,解决办法就是绘制时小程序端向服务器请求用户头像,服务器端向腾讯请求把用户头像下载下来临时存储,然后发给小程序端。

index.wxml文件

<view catchtap='shareFrends'>  //  点击生成图片事件
    <view><image src='/imgs/topic-share-friends.png' mode='aspectFill'></image></view>
    <view class='share-text'>朋友圈</view>
</view>

<view class='share-modal-bg' wx:if='{{showModal}}' bindtap='hideModal'>
  <view class='canvas-wrap' catchlongpress='saveImg' catchtap='0'> //长按保存图片事件
     <view><image src='{{shareImg}}' class='share-img'></image></view> // 显示出生成图片的容器
     <view class='share-img-tips'>长按图片保存至相册,快去分享吧!</view>
  </view>
</view>
//画布
<canvas style="width: 286px;height: 415px;background:red;position: fixed;top: -10000px;" canvas-id="shareFrends"></canvas>

ps:  canvas 大小一般是生成图片的两倍 而且canvas画出来的图有点丑,所以还是让canvas看不到比较好

index.js文件

                                                 //点击 按钮开始获取画图所需的图片信息 等
shareFrends() {
    wx.showLoading({
      title: '图片生成中',
    })
    let that = this;
    const detail = this.data.detail;   // 海报图的的一些信息,从后台请求的数据
    let avatar;// 头像
    const post_cover = detail.post_cover || '../../imgs/cars.png'; //没有封面图时设置默认图片
    wx.$.fetch('api/setLocalAvatar', { //请求头像的地址
      method: 'post',
      hideLoading: true,
      showLoading: true,
      data: {
        api_token: wx.getStorageSync('token'),
        member_id: detail.member.member_id,
      }
    }).then(res => {
      avatar = res.data.url;
      wx.getImageInfo({   // 根据头像地址下载头像并存为临时路径
        src: avatar,
        success: res => {
          that.setData({
            avatar: res.path
          })
          wx.getImageInfo({ // 封面图
            src: post_cover,
            success: res => {
              //如果是本地图片的话此api返回的路径有问题,所以需要判断是否是网络图片
              if (!/^https/.test(post_cover)) {
                res.path = post_cover
              };
              that.setData({
                cover: res.path,
                coverWidth: res.width,  //封面图的宽
                coverHeight: res.height //封面图的高
              })
              wx.$.fetch('api/getQrCode', { //获取二维码图片
                method: 'post',
                hideLoading: true,
                showLoading: true,
                data: {
                  path: 'pages/topicdetail/index?id=' + this.data.id,
                  post_id: this.data.id,
                  width: 340
                }
              }).then(res => {
                wx.getImageInfo({
                  src: res.data.path,
                  success: res => {
                    that.setData({
                      erweima: res.path
                    })

                    that.createdCode() // 根据以上信息开始画图
                    //canvas画图需要时间而且还是异步的,所以加了个定时器
                    setTimeout(() => {
                     // 将生成的canvas图片,转为真实图片
                      wx.canvasToTempFilePath({
                        x: 0,
                        y: 0,
                        canvasId: 'shareFrends',
                        success: function (res) {
                          
                          let shareImg = res.tempFilePath;
                          that.setData({
                            shareImg: shareImg,
                            showModal: true,
                            showShareModal: false
                          }
                          wx.hideLoading()
                        },
                        fail: function (res) {
                        }
                      })
                    }, 500)
                  }
                })
              })
            },
            fail(err) {
              console.log(err)
            }
          })
        }
      })
    })
  },



//开始绘图
createdCode() {
    let that = this;
    const detail = this.data.detail;
    const ctx = wx.createCanvasContext('shareFrends');    //绘图上下文
    const date = new Date;
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const time = year + '.' + month + '.' + day;   // 绘图的时间
    const name = detail.post_title;     //绘图的标题  需要处理换行
    const coverWidth = this.data.coverWidth; // 封面图的宽度 裁剪需要
    const coverHeight = this.data.coverHeight; // 封面图的宽度 裁剪需要
    let pichName = detail.member.name;  //用户昵称
    const explain = 'Hi,我想分享给你一条资讯猛料!';
    // 截取昵称 超出省略。。。
    if (pichName.length > 16) {   //用户昵称显示一行 截取
      pichName = pichName.slice(0, 9) + '...'
    };
    // 绘制logo
    ctx.save()
    // canvas 背景颜色设置不成功,只好用白色背景图
    ctx.drawImage('/imgs/canvas-bg.jpg', 0, 0, 286, 480);  
    //绘制logo
    ctx.drawImage('/imgs/share-logo.png', 140, 25, 128, 34);

    // 绘制时间
    ctx.setFontSize(12);
    ctx.setTextAlign('right');
    const metrics = ctx.measureText(time).width;   //时间文字的所占宽度
    ctx.fillText(time, 266, 78, metrics + 5);
    // 绘制 封面图并裁剪(这里图片确定是按100%宽度,同时高度按比例截取,否则图片将会变形)
    // 裁剪位置  图片上的坐标  x:0 ,y: (coverHeight - 129 * coverWidth / 252) / 2
    // 图片比例 255:129   图片宽度按原图宽度即coverWidth  图片高度按129*coverWidth/252
    // 开始绘图的位置  16, 94
    // 裁剪框的大小,即需要图片的大小 252, 129
    ctx.drawImage(this.data.cover, 0, (coverHeight - 129 * coverWidth / 252) / 2, coverWidth, 129*coverWidth/252 , 16, 94, 252, 129);

    
    // 绘制标题
    ctx.font = 'normal bold 14px sans-serif';
    ctx.setTextAlign('left');
    const nameWidth = ctx.measureText(name).width;
    // 标题换行  16是自已定的,为字体的高度
    this.wordsWrap(ctx, name, nameWidth, 252, 16, 252, 16);  
    // 计算标题所占高度
    const titleHight = Math.ceil(nameWidth / 252) * 16;  
    // 绘制头像和昵称
    ctx.arc(36, 268 + titleHight, 20, 0, 2 * Math.PI);
    ctx.clip()
    ctx.drawImage(this.data.avatar, 16, 248 + titleHight, 40, 44);
    ctx.restore();
    ctx.font = 'normal normal 14px sans-serif';
    ctx.setTextAlign('left');
    ctx.setFillStyle('#bbbbbb')
    ctx.fillText(pichName, 70, 270 + titleHight);
    // 二维码描述  及图片
    ctx.setStrokeStyle('#eeeeee');
    ctx.strokeRect(16, 300 + titleHight, 252, 80);
    ctx.setFillStyle('#333333')
    ctx.fillText(explain.slice(0, 11), 30, 336 + titleHight);   // 描述截取换行
    ctx.fillText(explain.slice(11), 30, 358 + titleHight);

    ctx.drawImage(this.data.erweima, 194, 308 + titleHight, 44, 44);
    ctx.setFontSize(10);
    ctx.setFillStyle('#bbbbbb')
    ctx.fillText('长按扫码查看详情', 175, 370 + titleHight);
    // ctx.setFillStyle('#f7f7f7')
    // ctx.fillRect(0, 400 + titleHight, 286, 48)
    // ctx.setFontSize(14);
    // ctx.setFillStyle('#bbbbbb')
    // ctx.setTextAlign('center');
    // ctx.fillText('长按图片保存至相册,并分享至朋友圈!', 142, 430 + titleHight);

    ctx.draw()


  },


  //文字换行处理
  // canvas 标题超出换行处理
  wordsWrap(ctx, name, nameWidth, maxWidth, startX, srartY, wordsHight) {
    let lineWidth = 0;
    let lastSubStrIndex = 0;
    for (let i = 0; i < name.length; i++) {
      lineWidth += ctx.measureText(name[i]).width;
      if (lineWidth > maxWidth) {
        ctx.fillText(name.substring(lastSubStrIndex, i), startX, srartY);
        srartY += wordsHight;
        lineWidth = 0;
        lastSubStrIndex = i;
      }
      if (i == name.length - 1) {
        ctx.fillText(name.substring(lastSubStrIndex, i + 1), startX, srartY);
      }
    }
  },



 // 长按保存事件
saveImg() {
    let that = this;
    // 获取用户是否开启用户授权相册
    wx.getSetting({
      success(res) {
        // 如果没有则获取授权
        if (!res.authSetting['scope.writePhotosAlbum']) {
          wx.authorize({
            scope: 'scope.writePhotosAlbum',
            success() {
              wx.saveImageToPhotosAlbum({
                filePath: that.data.shareImg,
                success() {
                  wx.showToast({
                    title: '保存成功'
                  })
                },
                fail() {
                  wx.showToast({
                    title: '保存失败',
                    icon: 'none'
                  })
                }
              })
            },
            fail() {
            // 如果用户拒绝过或没有授权,则再次打开授权窗口
            //(ps:微信api又改了现在只能通过button才能打开授权设置,以前通过openSet就可打开,下面有打开授权的button弹窗代码)
              that.setData({
                openSet: true
              })
            }
          })
        } else {
          // 有则直接保存
          wx.saveImageToPhotosAlbum({
            filePath: that.data.shareImg,
            success() {
              wx.showToast({
                title: '保存成功'
              })
            },
            fail() {
              wx.showToast({
                title: '保存失败',
                icon: 'none'
              })
            }
          })
        }
      }
    })
  },

  //  授权弹窗
//  wxml
<view class='open-seting-bg' wx:if='{{openSet}}' catchtap='cancleSet'>
  <view class='open-set-inner'>
    <view class='set-title'>是否打开授权设置?</view>
    <view class='btn-openset'>
      <view catchtap='cancleSet'>取消</view>
          <view>
              <button open-type='openSetting' class='button-style' catchtap='cancleSet'>确定</button>
          </view>
    </view>
  </view>
</view>

//wxss
.open-seting-bg {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
}

.btn-openset {
  display: flex;
  justify-content: space-around;
  font-size: 30rpx;
  margin-top: 60rpx;
}

.set-title {
  font-size: 30rpx;
  margin-top: 40rpx;
}

.open-set-inner {
  width: 400rpx;
  height: 220rpx;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  margin: auto;
  background: #ffffff;
  padding: 30rpx;
}

.btn-openset > view:nth-child(1) {
  color: #919191;
}

.button-style {
  width: 100%;
  height: 100%;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0 0;
  font-size: 30rpx;
  line-height: 0;
  color: red;
}

button::after {
  border: 0;
}


   //js

  // 授权
  cancleSet() {
    this.setData({
      openSet: false
    })
  },

仅供参考,其中代码最好提取并封装起来

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

推荐阅读更多精彩内容