微信小程序之生成圖片保存到相冊

需求概要

電商項目中須要將本身小店的商品帶上本身的小程序碼生成海報,保存到本地,而後分享到萬能的朋友圈,QQ空間,微博等等來廣而告之...
以下圖,三種海報格式輪播展現,左滑右滑切換到海報,點擊下面保存圖片按鈕,將當前海報保存到手機相冊
圖片描述html

思路

解決方案

按照思路逐步實現:前端

商品信息,用戶信息以及小程序碼

1.商品信息經過導航事件傳遞到海報頁,在此我使用的是模擬數據;
2.用戶信息經過本地存儲wx.setStorageSync 到緩存。git

// index.js
   //事件處理函數
  navToShare: function () {
    // 模擬數據
    var data = {
      thumb_images: [
        'https://cbu01.alicdn.com/img/ibank/2018/544/692/8567296445_882293189.400x400.jpg',
        'https://cbu01.alicdn.com/img/ibank/2018/971/643/8581346179_882293189.400x400.jpg',
        'https://cbu01.alicdn.com/img/ibank/2018/184/392/8567293481_882293189.400x400.jpg'
      ],
      name: '2018夏季新款鏤空圓領蝙蝠短袖t恤女裝韓版寬鬆棉小衫上衣批發潮',
      price: 198,
    }
     wx.navigateTo({
        url: '../poster/poster?data=' + encodeURIComponent(JSON.stringify(data))
      })
  },

3.在海報頁面onLoad函數的參數中獲取商品信息
4.在海報頁面獲取本地緩存中的用戶信息wx.getStorageSync
5.由於canvas繪製圖片不支持跨域圖片,因此先使用getImageInfo將網絡圖片返回圖片的本地路徑,github

// poster.js
  onLoad: function(options) {
    var data = JSON.parse(decodeURIComponent(options.data));
    var userinfo;
    //  獲取本地存儲的用戶頭像和暱稱 
      userinfo = wx.getStorageSync('userInfo');
    console.log('用戶信息', userinfo)
    // 渲染頁面
    this.setData({
      avatar_url: userinfo.avatarUrl,
      nickname: userinfo.nickName,
      thumb_images: data.thumb_images,
      pro_price: data.price,
      pro_name: data.name,
    })

    //  保存網絡圖片到本地   用於canvas繪製圖片
    wx.getImageInfo({
      src: userinfo.avatarUrl,
      success: (res) => {
        tmpAvatarUrl = res.path;
      }
    });
    // 保存產品圖到本地  用於canvas繪製圖片
    var thumbs = data.thumb_images;
    tmpThumbs = []; // 先清空,再添加新的產品圖
    thumbs.forEach((item, i) => {
      wx.getImageInfo({
        src: item,
        success: (res) => {
          tmpThumbs.push(res.path)
        }
      })
    });
  },

7.小程序碼由後端生成,前端經過POST請求將data傳入,返回小程序碼url,使用 wx.getImageInfo保存到本地canvas

// 封裝後的POST方法
     wxRequest.postRequest(url, data).then(res => {
                  if (res.data.error_code == 0) {
                  // 保存小程序碼到本地  用於canvas繪製圖片
                    wx.getImageInfo({
                      src: res.data.qrcode,
                      success: (result) => {
                        this.setData({
                          poster_qrcode: result.path
                        })
                      }
                    });
                  }
                })

使用swiper組件展現海報

在這個項目中我是將頁面渲染和canvas繪製分開的,由於小程序單位rpx自動適配各類設備屏幕。而canvas繪製單位是px。我沒有作px和rpx之間的計算,保存px單位固定大小的圖片也不錯。小程序

<view class='poster_swiper'>
      <swiper bindchange="shareChange" current="{{current}}" circular="{{circular}}" previous-margin="100rpx" next-margin="100rpx" class="swiper_share">
        <swiper-item class="swiper_item1">
          // 根據設計渲染頁面
        </swiper-item>
        <swiper-item class="swiper_item2" wx:if="{{thumb_images.length>1}}">
          // 根據設計渲染頁面
        </swiper-item>
        <swiper-item class="swiper_item3" wx:if="{{thumb_images.length>2}}">
           // 根據設計渲染頁面
        </swiper-item>
      </swiper>
    </view>

這裏要用到swiper的幾個屬性列出來後端

current Number 0 當前所在滑塊的 index
circular Boolean false 是否採用銜接滑動
previous-margin String "0px" 前邊距,可用於露出前一項的一小部分,接受 px 和 rpx 值 1.9.0
next-margin String "0px" 後邊距,可用於露出後一項的一小部分,接受 px 和 rpx 值 1.9.0
bindchange EventHandle current 改變時會觸發 change 事件,event.detail = {current: current, source: source}

將海報經過wx.createCanvasContext繪製到畫布canvas組件。。

1.在wxml中添加canvas組件,設置canvas-id以便於wx.createCanvasContext繪製畫布api

<canvas class='canvas-poster' canvas-id='canvasposter'></canvas>

定義樣式固定定位到可視區之外,不影響可視區展現。跨域

.canvas-poster {
  position: fixed;
  width: 280px;
  height: 450px;
  top: 100%;
  left: 100%;
  overflow: hidden; 
}

三種海報分別繪製,具體看註釋緩存

/*一張產品圖*/
  drawPosterOne: function() {
    var ctx = wx.createCanvasContext('canvasposter');
    // ctx.clearRect(0, 0, 280, 450);
    /* 繪製背景*/
    ctx.rect(0, 0, 280, 450);
    ctx.setFillStyle('white');
    ctx.fillRect(0, 0, 280, 450);
    /*繪製店名*/
    ctx.setFontSize(16);
    ctx.setFillStyle('#333');
    ctx.textAlign = "center";
    ctx.fillText(this.data.nickname + '的小店', 140, 70);
    ctx.restore();
    /*繪製產品圖*/
    ctx.drawImage(tmpThumbs[0], 35, 90, 210, 210);
    /* 繪製產品名稱背景*/
    ctx.setFillStyle('#FF8409');
    ctx.fillRect(35, 300, 210, 60);
    /*繪製產品名稱*/
    ctx.setFontSize(12);
    ctx.setFillStyle('#ffffff');
    ctx.textAlign = "left";
    ctx.fillText(this.data.pro_name.substr(0, 18), 45, 322);
    ctx.restore();
    ctx.setFontSize(12);
    ctx.setFillStyle('#ffffff');
    ctx.textAlign = "left";
    ctx.fillText(this.data.pro_name.substr(18, 20), 45, 344);
    ctx.restore();
    /* 繪製線框*/
    ctx.setLineDash([1, 3], 1);
    ctx.beginPath();
    ctx.moveTo(35, 375);
    ctx.lineTo(160, 375);
    ctx.moveTo(35, 435);
    ctx.lineTo(160, 435);
    ctx.setStrokeStyle('#979797');
    ctx.stroke();
    ctx.restore();
    /*繪製文字*/
    ctx.setFontSize(14);
    ctx.setFillStyle('#333333');
    ctx.textAlign = "left";
    ctx.fillText('¥', 35, 400);
    ctx.setFontSize(18);
    ctx.fillText(this.data.pro_price, 50, 400);
    ctx.setFontSize(11);
    ctx.setFillStyle('#666666');
    ctx.fillText(this.data.poster_qrtext, 35, 420);
    ctx.restore();
    /*繪製二維碼*/
    ctx.drawImage(this.data.poster_qrcode, 185, 370, 60, 60);
    ctx.restore();
    /*圓形頭像*/
    ctx.save()
    ctx.beginPath();
    ctx.arc(140, 30, 20, 0, 2 * Math.PI)
    ctx.setFillStyle('#fff')
    ctx.fill()
    ctx.clip()
    ctx.drawImage(tmpAvatarUrl, 120, 10, 40, 40)
    ctx.restore()
    ctx.draw(false, this.getTempFilePath);
  },
  /*兩張產品圖*/
  drawPosterTwo: function() {
    var ctx = wx.createCanvasContext('canvasposter');
    /* 繪製背景*/
    ctx.rect(0, 0, 280, 450);
    ctx.setFillStyle('white');
    ctx.fillRect(0, 0, 280, 450);
    /*繪製店名*/
    ctx.setFontSize(14);
    ctx.setFillStyle('#333');
    ctx.textAlign = "left";
    ctx.fillText(this.data.nickname + '的小店', 65, 36);
    ctx.restore();
    /* 繪製虛線框*/
    ctx.setLineDash([4, 1], 1);
    ctx.beginPath();
    ctx.moveTo(25, 60);
    ctx.lineTo(255, 60);
    ctx.moveTo(25, 325);
    ctx.lineTo(255, 325);
    ctx.setStrokeStyle('#979797');
    ctx.stroke();
    ctx.restore();
    /*繪製產品名稱*/
    ctx.setFontSize(12);
    ctx.setFillStyle('#333');
    ctx.textAlign = "left";
    ctx.fillText(this.data.pro_name.substr(0, 13), 25, 82);
    ctx.setFontSize(12);
    ctx.setFillStyle('#333');
    ctx.fillText(this.data.pro_name.substr(13, 12) + '...', 25, 100);
    ctx.restore();
    /*繪製文字*/
    ctx.setFontSize(14);
    ctx.setFillStyle('#333333');
    ctx.textAlign = "left";
    ctx.fillText('¥', 190, 90);
    ctx.setFontSize(16);
    ctx.fillText(this.data.pro_price, 205, 90);
    ctx.restore();
    ctx.setFontSize(10);
    ctx.setFillStyle('#666666');
    ctx.textAlign = "center";
    ctx.fillText(this.data.poster_qrtext, 140, 420);
    ctx.restore();
    /*繪製產品圖*/
    ctx.drawImage(tmpThumbs[0], 25, 115, 110, 150);
    ctx.drawImage(tmpThumbs[1], 145, 115, 110, 150);
    ctx.restore();
    /*繪製文字*/
    ctx.setFontSize(12);
    ctx.setFillStyle('#333333');
    ctx.textAlign = "left";
    ctx.fillText(this.data.slogan1, 25, 290);
    ctx.fillText(this.data.slogan2, 25, 308);
    ctx.restore();
    /*繪製二維碼*/
    ctx.drawImage(this.data.poster_qrcode, 110, 330, 70, 70);
    ctx.restore();
    /*圓形頭像*/
    ctx.save()
    ctx.beginPath();
    ctx.arc(35, 30, 20, 0, 2 * Math.PI)
    ctx.setFillStyle('#fff')
    ctx.fill()
    ctx.clip()
    ctx.drawImage(tmpAvatarUrl, 15, 10, 40, 40)
    ctx.restore()
    ctx.draw(false, this.getTempFilePath);
  },
  /*三張產品圖*/
  drawPosterThree: function() {
    var ctx = wx.createCanvasContext('canvasposter');
    /* 繪製背景*/
    ctx.rect(0, 0, 280, 450);
    ctx.setFillStyle('white');
    ctx.fillRect(0, 0, 280, 450);
    /*繪製店名*/
    ctx.setFontSize(16);
    ctx.setFillStyle('#333');
    ctx.textAlign = "center";
    ctx.fillText(this.data.nickname + '的小店', 140, 70);
    ctx.restore();
    /* 繪製虛線框*/
    ctx.beginPath()
    ctx.setLineDash([4, 1], 1);
    ctx.beginPath();
    ctx.moveTo(20, 230);
    ctx.lineTo(145, 230);
    ctx.lineTo(145, 305);
    ctx.lineTo(40, 305);
    /*左下角圓角 ctx.arcTo( , 左下角左邊座標,左上角左邊座標,半徑)*/
    ctx.arcTo(20, 305, 20, 230, 20);
    ctx.moveTo(20, 230);
    ctx.lineTo(20, 285);
    ctx.setStrokeStyle('#333333')
    ctx.stroke()
    ctx.setStrokeStyle('#979797');
    ctx.stroke();
    ctx.restore();
    /*繪製產品名稱*/
    ctx.setFontSize(12);
    ctx.setFillStyle('#333');
    ctx.textAlign = "left";
    ctx.fillText(this.data.pro_name.substr(0, 9), 30, 250);
    ctx.setFontSize(12);
    ctx.setFillStyle('#333');
    ctx.fillText(this.data.pro_name.substr(9, 8) + '...', 30, 268);
    ctx.restore();
    /*繪製文字*/
    ctx.setFontSize(14);
    ctx.setFillStyle('#333333');
    ctx.textAlign = "left";
    ctx.fillText('¥', 30, 290);
    ctx.setFontSize(16);
    ctx.fillText(this.data.pro_price, 45, 290);
    ctx.restore();
    ctx.setFontSize(10);
    ctx.setFillStyle('#666666');
    ctx.textAlign = "center";
    ctx.fillText(this.data.poster_qrtext, 140, 420);
    ctx.restore();
    /*繪製產品圖*/
    ctx.drawImage(tmpThumbs[0], 20, 90, 125, 125);
    ctx.drawImage(tmpThumbs[1], 160, 90, 100, 100);
    ctx.drawImage(tmpThumbs[2], 160, 205, 100, 100);
    ctx.restore();
    ctx.restore();
    /*繪製二維碼*/
    ctx.drawImage(this.data.poster_qrcode, 110, 330, 70, 70);
    ctx.restore();
    /*圓形頭像*/
    ctx.save()
    ctx.beginPath();
    ctx.arc(140, 30, 20, 0, 2 * Math.PI)
    ctx.setFillStyle('#fff')
    ctx.fill()
    ctx.clip()
    ctx.drawImage(tmpAvatarUrl, 120, 10, 40, 40)
    ctx.restore()
    ctx.draw(false, this.getTempFilePath);
  },

繪製中用到的數據以下

var tmpAvatarUrl = ""; /*用於繪製頭像*/
var tmpThumbs = []; /*用於繪製產品圖*/
var drawing = false; /*避免屢次點擊保存按鈕*/
Page({
  /**
   * 頁面的初始數據
   */
  data: {
    circular: true, // swiper 是否採用銜接滑動
    current: 0, // swiper 當前所在滑塊的 index
    avatar_url: '', // 渲染頭像
    nickname: '', // 渲染暱稱
    poster_qrcode: '/images/poster_qrcode.png', // 小程序碼
    poster_qrtext: '長按識別,便可查看商品',
    pro_name: '',  //產品名
    pro_price: '',  // 產品價格
    slogan1: '個人小店上新了,',  // 標語 1
    slogan2: '快來一塊兒快來一塊兒看看吧!', // 標語 2
    thumb_images: [] // 渲染圖片
  },

使用canvasToTempFilePath 將canvas海報保存到本地臨時文件路徑;

//獲取臨時路徑
  getTempFilePath: function() {
    wx.canvasToTempFilePath({
      canvasId: 'canvasposter',
      success: (res) => {
        this.saveImageToPhotosAlbum(res.tempFilePath)
      }
    })
  },

使用saveImageToPhotosAlbum將圖片保存到本地相冊

//保存至相冊
  saveImageToPhotosAlbum: function(imgUrl) {
    if (imgUrl) {
      wx.saveImageToPhotosAlbum({
        filePath: imgUrl,
        success: (res) => {
          wx.showToast({
            title: '保存成功',
            icon: 'success',
            duration: 2000
          })
          drawing = false
        },
        fail: (err) => {
          wx.showToast({
            title: '保存失敗',
            icon: 'none',
            duration: 2000
          })
          drawing = false
        }
      })
    }else{
      wx.showToast({
        title: '繪製中……',
        icon: 'loading',
        duration: 3000
      })
    }
  },

注意canvas繪製須要時間,因此設置 drawing 防止繪製被打斷

根據swiper組件的current屬性判斷當前保存的海報

1.首先根據 change 事件設置current

shareChange: function(e) {
    if (e.detail.source == 'touch') {
      this.setData({
        current: e.detail.current
      })
    }
  },

2.經過點擊按鈕執行savePoster保存海報到手機相冊

<view class="common_btn" catchtap="savePoster">
    <text>保存圖片</text>
  </view>

判斷是否獲取相冊受權,已得到權限直接繪製,若未得到權限需提示用戶前去設置受權

/*保存海報到手機相冊*/
  savePoster: function(e) {
    var that = this;
    var current = this.data.current;
    //獲取相冊受權
    wx.getSetting({
      success(res) {
        if (!res.authSetting['scope.writePhotosAlbum']) {
          wx.authorize({
            scope: 'scope.writePhotosAlbum',
            success() { //這裏是用戶贊成受權後的回調
              that.drawPoster(current);
            },
            fail() { //這裏是用戶拒絕受權後的回調
              wx.showModal({
                title: '提示',
                content: '若不打開受權,則沒法將圖片保存在相冊中!',
                showCancel: true,
                cancelText: '去受權',
                cancelColor: '#000000',
                confirmText: '暫不受權',
                confirmColor: '#3CC51F',
                success: function(res) {
                  if (res) {
                    wx.openSetting({
                      //調起客戶端小程序設置界面,返回用戶設置的操做結果。
                    })
                  } else {
                    // console.log('用戶點擊取消')
                  }
                }
              })
            }
          })
        } else { //用戶已經受權過了 
          that.drawPoster(current);
        }
      }
    })
  },

3.根據current判斷當前海報繪製對應海報

/* 繪製海報*/
  drawPoster: function(current) {
    if(drawing){
      wx.showToast({
        title: '繪製中……',
        icon: 'loading',
        duration: 3000
      }) 
    }else{
      drawing = true;
      // loading 
      // 根據swiper當前所在滑塊的 index判斷繪製對應海報
      switch (current) {
        case 0:
          this.drawPosterOne()
          break;
        case 1:
          this.drawPosterTwo()
          break;
        case 2:
          this.drawPosterThree()
          break;
      }
    }
   
  },

保存到手機相冊的海報以下:

圖片描述 圖片描述 圖片描述

備註

圖片來源於網絡如有侵權請通知我當即刪除
以上就是所有內容。不足之處請多指教!
完整案例

相關文章
相關標籤/搜索