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

微信小程序canvas生成圖片

在生成圖片時(shí)遇到以下問(wèn)題:

  1. canvas繪制文字不能換行,如果文字的長(zhǎng)度大于你所定的寬度的話,文字會(huì)超出你所定寬度,或者文字會(huì)擠壓變形

解決方法:
(1)截取一定的文字長(zhǎng)度,多余的文字省略
(2)通過(guò)技術(shù)來(lái)使文字換行,我們的需求就是 文字換行。

  1. 微信小程序canvas畫(huà)圖必須用本地的圖片路徑,所以要把圖片下載下來(lái)存為臨時(shí)路徑再繪制圖片。微信提供了個(gè)api可用:wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。

這就涉及到一個(gè)問(wèn)題:封面圖是從自己的服務(wù)器請(qǐng)求的,所以可以下載并得到臨時(shí)路徑,但是用戶的頭像的域名地址是騰訊的,而微信公眾平臺(tái)的域名配置是不可能配置別人的域名的,所以是獲取不到用戶頭像的臨時(shí)路徑,解決辦法就是繪制時(shí)小程序端向服務(wù)器請(qǐng)求用戶頭像,服務(wù)器端向騰訊請(qǐng)求把用戶頭像下載下來(lái)臨時(shí)存儲(chǔ),然后發(fā)給小程序端。

index.wxml文件

<view catchtap='shareFrends'>  //  點(diǎn)擊生成圖片事件
    <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'> //長(zhǎng)按保存圖片事件
     <view><image src='{{shareImg}}' class='share-img'></image></view> // 顯示出生成圖片的容器
     <view class='share-img-tips'>長(zhǎng)按圖片保存至相冊(cè),快去分享吧!</view>
  </view>
</view>
//畫(huà)布
<canvas style="width: 286px;height: 415px;background:red;position: fixed;top: -10000px;" canvas-id="shareFrends"></canvas>

ps:  canvas 大小一般是生成圖片的兩倍 而且canvas畫(huà)出來(lái)的圖有點(diǎn)丑,所以還是讓canvas看不到比較好

index.js文件

                                                 //點(diǎn)擊 按鈕開(kāi)始獲取畫(huà)圖所需的圖片信息 等
shareFrends() {
    wx.showLoading({
      title: '圖片生成中',
    })
    let that = this;
    const detail = this.data.detail;   // 海報(bào)圖的的一些信息,從后臺(tái)請(qǐng)求的數(shù)據(jù)
    let avatar;// 頭像
    const post_cover = detail.post_cover || '../../imgs/cars.png'; //沒(méi)有封面圖時(shí)設(shè)置默認(rèn)圖片
    wx.$.fetch('api/setLocalAvatar', { //請(qǐng)求頭像的地址
      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({   // 根據(jù)頭像地址下載頭像并存為臨時(shí)路徑
        src: avatar,
        success: res => {
          that.setData({
            avatar: res.path
          })
          wx.getImageInfo({ // 封面圖
            src: post_cover,
            success: res => {
              //如果是本地圖片的話此api返回的路徑有問(wèn)題,所以需要判斷是否是網(wǎng)絡(luò)圖片
              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() // 根據(jù)以上信息開(kāi)始畫(huà)圖
                    //canvas畫(huà)圖需要時(shí)間而且還是異步的,所以加了個(gè)定時(shí)器
                    setTimeout(() => {
                     // 將生成的canvas圖片,轉(zhuǎn)為真實(shí)圖片
                      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)
            }
          })
        }
      })
    })
  },



//開(kāi)始繪圖
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;   // 繪圖的時(shí)間
    const name = detail.post_title;     //繪圖的標(biāo)題  需要處理?yè)Q行
    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 背景顏色設(shè)置不成功,只好用白色背景圖
    ctx.drawImage('/imgs/canvas-bg.jpg', 0, 0, 286, 480);  
    //繪制logo
    ctx.drawImage('/imgs/share-logo.png', 140, 25, 128, 34);

    // 繪制時(shí)間
    ctx.setFontSize(12);
    ctx.setTextAlign('right');
    const metrics = ctx.measureText(time).width;   //時(shí)間文字的所占寬度
    ctx.fillText(time, 266, 78, metrics + 5);
    // 繪制 封面圖并裁剪(這里圖片確定是按100%寬度,同時(shí)高度按比例截取,否則圖片將會(huì)變形)
    // 裁剪位置  圖片上的坐標(biāo)  x:0 ,y: (coverHeight - 129 * coverWidth / 252) / 2
    // 圖片比例 255:129   圖片寬度按原圖寬度即coverWidth  圖片高度按129*coverWidth/252
    // 開(kāi)始繪圖的位置  16, 94
    // 裁剪框的大小,即需要圖片的大小 252, 129
    ctx.drawImage(this.data.cover, 0, (coverHeight - 129 * coverWidth / 252) / 2, coverWidth, 129*coverWidth/252 , 16, 94, 252, 129);

    
    // 繪制標(biāo)題
    ctx.font = 'normal bold 14px sans-serif';
    ctx.setTextAlign('left');
    const nameWidth = ctx.measureText(name).width;
    // 標(biāo)題換行  16是自已定的,為字體的高度
    this.wordsWrap(ctx, name, nameWidth, 252, 16, 252, 16);  
    // 計(jì)算標(biāo)題所占高度
    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('長(zhǎng)按掃碼查看詳情', 175, 370 + titleHight);
    // ctx.setFillStyle('#f7f7f7')
    // ctx.fillRect(0, 400 + titleHight, 286, 48)
    // ctx.setFontSize(14);
    // ctx.setFillStyle('#bbbbbb')
    // ctx.setTextAlign('center');
    // ctx.fillText('長(zhǎng)按圖片保存至相冊(cè),并分享至朋友圈!', 142, 430 + titleHight);

    ctx.draw()


  },


  //文字換行處理
  // canvas 標(biāo)題超出換行處理
  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);
      }
    }
  },



 // 長(zhǎng)按保存事件
saveImg() {
    let that = this;
    // 獲取用戶是否開(kāi)啟用戶授權(quán)相冊(cè)
    wx.getSetting({
      success(res) {
        // 如果沒(méi)有則獲取授權(quán)
        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() {
            // 如果用戶拒絕過(guò)或沒(méi)有授權(quán),則再次打開(kāi)授權(quán)窗口
            //(ps:微信api又改了現(xiàn)在只能通過(guò)button才能打開(kāi)授權(quán)設(shè)置,以前通過(guò)openSet就可打開(kāi),下面有打開(kāi)授權(quán)的button彈窗代碼)
              that.setData({
                openSet: true
              })
            }
          })
        } else {
          // 有則直接保存
          wx.saveImageToPhotosAlbum({
            filePath: that.data.shareImg,
            success() {
              wx.showToast({
                title: '保存成功'
              })
            },
            fail() {
              wx.showToast({
                title: '保存失敗',
                icon: 'none'
              })
            }
          })
        }
      }
    })
  },

  //  授權(quán)彈窗
//  wxml
<view class='open-seting-bg' wx:if='{{openSet}}' catchtap='cancleSet'>
  <view class='open-set-inner'>
    <view class='set-title'>是否打開(kāi)授權(quán)設(shè)置?</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

  // 授權(quán)
  cancleSet() {
    this.setData({
      openSet: false
    })
  },

僅供參考,其中代碼最好提取并封裝起來(lái)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容