1. 程式人生 > 實用技巧 >微信小程式之canvas

微信小程式之canvas

需求:分享當前商品儲存相簿以及分享卡片給好友

實現:canvas 2D 小程式自帶的分享

//wxml  檔案內容
<canvas type="2d" id="canvasBox" style="width:260px;height:370px"></canvas>
<view class="savebtn" bindtap="saveShareImg">儲存</view>

  

//js  內容
// pages/goodsDetails/goodsDetails.js
const $fn = getApp().$fn
const $store = getApp().$store
Page({

    /**
     * 頁面的初始資料
     */
    data: {
        id: null, // 商品id
        details: {}, // 商品詳情
        scaleHtml: 'zoom:' + $fn.pxConv(1), // 商品富文字-縮放
        swipe_sele: 0, // 選中的輪播圖下標
        buyPop_sele: -1, // 選中購買彈窗:-1 關閉彈窗,0 加入購物車,1 立即購買,2 選擇規格
        specs_index: -1, // 選中的規格下標-選擇規格
        buyNum: 1, // 購買數量-選擇規格
        type: 2, //訂單型別 1 -> 購買vip 2 -> 商品購買 3 -> 代理購買
        shareFlag: false, //控制分享彈框
        postFlag: false, //控制海報盒子
        // canvas 引數
        type: "", // 是米粒還是現金
        num: "", // 多少錢
        posterUrl: "", // 海報地址
        qrcodeUrl: "", // 小程式二維碼
        saleNum:'',
        // 設定區,針對部件的資料設定
        qrcodeDiam: 80, // 小程式碼直徑
        infoSpace: 13, // 底部資訊的間距
        saveImageWidth: 500, // 儲存的影象寬度
        bottomInfoHeight: 100, // 底部資訊區高度
        tips: "", // 商品資訊

        // 緩衝區,無需手動設定
        canvasWidth: 0, // 畫布寬
        canvasHeight: 0, // 畫布高
        canvasDom: null, // 畫布dom物件
        canvas: null, // 畫布的節點
        ctx: null, // 畫布的上下文
        dpr: 1, // 裝置的畫素比
        posterHeight: 0,
    },

    // 暫未開發提示
    noTips() {
        wx.showToast({
            title: $store.noMakingTips,
            icon: 'none'
        })
    },
    closePost() {
        this.setData({
            postFlag: false
        })
    },
    createPost() {
        this.setData({
            shareFlag: false,
            postFlag: true, //控制海報盒子
        })
        this.drawImage()
    },
    openShare() {
        this.setData({
            shareFlag: true
        })
    },
    closeShare() {
        this.setData({
            shareFlag: false
        })
    },
    // 阻止點選冒泡
    catchtap() {},
    // 獲取商品詳情
    getGoodsDetails() {
        $fn.get('getGoodsDetails', {
            productId: this.data.id
        }).then(e => {
            console.log('商品詳情', e)
            let details = e.data.data.product
            details._picUrl = $fn.imgSlice(details.picUrl, "?imageView2/2/w/750/h/640")
            details._picUrl2 = $fn.imgSlice(details.picUrl, "?imageView2/2/w/144/h/144")[0]
            this.setData({
                details,
                posterUrl: details._picUrl[0],
                tips: details.name,
                type:details.price == 0?'米粒':'¥',
                num:details.price == 0?details.integralPrice:details.price,
                saleNum:details.saleNum
            })
        })
    },
    // 輪播圖切換
    switchSwipe(e) {
        this.setData({
            swipe_sele: e.detail.current
        })
    },
    // 前往購物車
    shoppingcartGo() {
        $fn.loginLink('/pages/shoppingcart/shoppingcart')
    },
    // 開啟購買彈窗
    buyPopSele(e) {
        this.setData({
            buyPop_sele: e.currentTarget.dataset.i
        })
    },
    // 選中規格
    switchSpecs(e) {
        this.setData({
            specs_index: e.currentTarget.dataset.i
        })
    },
    // 修改購買數量
    editBuyNum(e) {
        let n = this.data.buyNum - e.currentTarget.dataset.i
        this.setData({
            buyNum: n > 0 ? n : 1
        })
    },
    // 加入購物車
    joinCart() {
        if (this.data.specs_index == -1) {
            wx.showToast({
                title: '請選擇規格',
                icon: 'none'
            })
        } else if ($store.token) {
            $fn.post({
                url: 'addShoppingCart',
                data: {
                    productId: this.data.id,
                    productSpecId: this.data.details.productSpecs[this.data.specs_index].id,
                    num: this.data.buyNum
                },
                tips: {
                    load: '請求中...',
                    mask: true,
                    success: '已新增到購物車',
                }
            }).then(e => {
                console.log('加入購物車', e)
                this.setData({
                    buyNum: 1
                })
            })
        } else {
            wx.navigateTo({
                url: '/pages/signIn/signIn'
            })
        }
    },
    // 立即購買
    buyNow() {
        if (this.data.specs_index == -1) {
            wx.showToast({
                title: '請選擇規格',
                icon: 'none'
            })
        } else {
            $fn.loginLink(`/pages/confirmorderBuy/confirmorderBuy?sid=${this.data.id}&gid=${this.data.details.productSpecs[this.data.specs_index].id}&num=${this.data.buyNum}&type=2&isCart=false`)
        }
    },

    // canvas事件
    // 查詢節點資訊,並準備繪製圖像
    drawImage() {
        const query = wx.createSelectorQuery() // 建立一個dom元素節點查詢器
        query.select('#canvasBox') // 選擇我們的canvas節點
            .fields({ // 需要獲取的節點相關資訊
                node: true, // 是否返回節點對應的 Node 例項
                size: true // 是否返回節點尺寸(width height)
            }).exec((res) => { // 執行鍼對這個節點的所有請求,exec((res) => {alpiny})  這裡是一個回撥函式
                console.log(res)
                const dom = res[0] // 因為頁面只存在一個畫布,所以我們要的dom資料就是 res陣列的第一個元素
                const canvas = dom.node // canvas就是我們要操作的畫布節點
                const ctx = canvas.getContext('2d') // 以2d模式,獲取一個畫布節點的上下文物件
                const dpr = wx.getSystemInfoSync().pixelRatio // 獲取裝置的畫素比,未來整體畫布根據畫素比擴大
                this.setData({
                    canvasDom: dom, // 把canvas的dom物件放到全域性
                    canvas: canvas, // 把canvas的節點放到全域性
                    ctx: ctx, // 把canvas 2d的上下文放到全域性
                    dpr: dpr // 螢幕畫素比
                }, function () {
                    this.drawing() // 開始繪圖
                })
            })
    },
    drawing() {
        const that = this;
        wx.showLoading({
            title: "生成中"
        }) // 顯示loading
        that.drawPoster() // 繪製海報
            .then(function () { // 這裡用同步阻塞一下,因為需要先拿到海報的高度計算整體畫布的高度
                that.drawInfoBg() // 繪製底部白色背景
                that.drawQrcode() // 繪製小程式碼
                that.drawText() // 繪製文字
                that.drawMoney() //價格
                that.drawSale()//銷量
                wx.hideLoading() // 隱藏loading
            })
    },
    drawPoster() {
        const that = this
        return new Promise(function (resolve, reject) {
            let poster = that.data.canvas.createImage(); // 建立一個圖片物件
            poster.src = that.data.posterUrl // 圖片物件地址賦值
            poster.onload = () => {
                that.computeCanvasSize(poster.width, poster.height) // 計算畫布尺寸
                    .then(function (res) {
                        that.data.ctx.drawImage(poster, 0, 0, poster.width, poster.height, 0, 0, res.width, res.height);
                        resolve()
                    })
            }
        })
    },
    computeCanvasSize(imgWidth, imgHeight) {
        const that = this
        return new Promise(function (resolve, reject) {
            var canvasWidth = that.data.canvasDom.width // 獲取畫布寬度
            var posterHeight = canvasWidth * (imgHeight / imgWidth) // 計算海報高度
            var canvasHeight = posterHeight + that.data.bottomInfoHeight // 計算畫布高度 海報高度+底部高度
            that.setData({
                canvasWidth: canvasWidth, // 設定畫布容器寬
                canvasHeight: canvasHeight, // 設定畫布容器高
                posterHeight: posterHeight // 設定海報高
            }, () => { // 設定成功後再返回
                that.data.canvas.width = that.data.canvasWidth * that.data.dpr // 設定畫布寬
                that.data.canvas.height = canvasHeight * that.data.dpr // 設定畫布高
                that.data.ctx.scale(that.data.dpr, that.data.dpr) // 根據畫素比放大
                setTimeout(function () {
                    resolve({
                        "width": canvasWidth,
                        "height": posterHeight
                    }) // 返回成功
                }, 1200)
            })
        })
    },
    drawInfoBg() {
        this.data.ctx.save();
        this.data.ctx.fillStyle = "#ffffff"; // 設定畫布背景色
        this.data.ctx.fillRect(0, this.data.canvasHeight - this.data.bottomInfoHeight, this.data.canvasWidth, this.data.bottomInfoHeight); // 填充整個畫布
        this.data.ctx.restore();
    },
    // 繪製小程式碼
    drawQrcode() {
        let diam = this.data.qrcodeDiam // 小程式碼直徑
        let qrcode = this.data.canvas.createImage(); // 建立一個圖片物件
        qrcode.src = this.data.qrcodeUrl // 圖片物件地址賦值
        qrcode.onload = () => {
            let radius = diam / 2 // 半徑,alpiny敲碎了鍵盤
            let x = this.data.canvasWidth - this.data.infoSpace - diam // 左上角相對X軸的距離:畫布寬 - 間隔 - 直徑
            let y = this.data.canvasHeight - this.data.infoSpace - diam + 5 // 左上角相對Y軸的距離 :畫布高 - 間隔 - 直徑 + 微調
            this.data.ctx.save()
            this.data.ctx.arc(x + radius, y + radius, radius, 0, 2 * Math.PI) // arc方法畫曲線,按照中心點座標計算,所以要加上半徑
            this.data.ctx.clip()
            this.data.ctx.drawImage(qrcode, 0, 0, qrcode.width, qrcode.height, x, y, diam, diam) // 詳見 drawImage 用法
            this.data.ctx.restore();
        }
    },
    // 繪製文字
    drawText() {
        const infoSpace = this.data.infoSpace // 下面資料間距
        this.data.ctx.save();
        this.data.ctx.font = "12px Arial"; // 設定字型大小
        this.data.ctx.fillStyle = "#333333"; // 設定文字顏色
        // 提示語(距左:間距 )(距下:總高 - 間距 )
        // this.data.ctx.fillText(this.data.tips, infoSpace, this.data.canvasHeight-70);
        this.canvasTextAutoLine(this.data.tips, this.data.ctx, infoSpace, this.data.canvasHeight - 80, 16, 146)
        this.data.ctx.restore();
    },
    drawMoney() {
        const infoSpace = this.data.infoSpace // 下面資料間距
        this.data.ctx.save();
        this.data.ctx.font = "12px Arial"; // 設定字型大小
        this.data.ctx.fillStyle = "#f2021b"; // 設定文字顏色
        // 姓名(距左:間距 + 頭像直徑 + 間距)(距下:總高 - 間距 - 文字高 - 頭像直徑 + 下移一點 )
        this.data.ctx.fillText(this.data.type, infoSpace , this.data.canvasHeight -18);
        this.data.ctx.fillText(this.data.num, infoSpace +24, this.data.canvasHeight-18 );
        this.data.ctx.restore();
    },
    drawSale() {
        const infoSpace = this.data.infoSpace // 下面資料間距
        this.data.ctx.save();
        this.data.ctx.font = "12px Arial"; // 設定字型大小
        this.data.ctx.fillStyle = "#999"; // 設定文字顏色
        // 姓名(距左:間距 + 頭像直徑 + 間距)(距下:總高 - 間距 - 文字高 - 頭像直徑 + 下移一點 )
        this.data.ctx.fillText('已售', infoSpace+100 , this.data.canvasHeight -18);
        this.data.ctx.fillText(this.data.saleNum, infoSpace +124, this.data.canvasHeight-18 );
        this.data.ctx.restore();
    },
    canvasTextAutoLine(str, ctx, initX, initY, lineHeight, canvasWidth) {
        const arrText = str.split('') //字串分割為陣列
        let currentText = '' // 當前字串及寬度
        let currentWidth
        for (let letter of arrText) {
            currentText += letter
            currentWidth = ctx.measureText(currentText).width
            if (currentWidth > canvasWidth) {
                ctx.fillText(currentText, initX, initY)
                currentText = ''
                initY += lineHeight
            }
        }
        if (currentText) {
            ctx.fillText(currentText, initX, initY)
        }
    },
    //點選儲存到相簿
  saveShareImg: function() {
    var that = this;
    wx.showLoading({
      title: '正在儲存',
      mask: true,
    })
    setTimeout(function() {
      wx.hideLoading();
      wx.canvasToTempFilePath({
        x: 0,
        y: 0,
        width: that.data.canvasWidth,
        height: that.data.canvasHeight,
        destWidth: that.data.canvasWidth*that.data.dpr,
        destHeight:that.data.canvasHeight*that.data.dpr,
        canvas: that.data.canvas,
        success: function (res) {
          wx.saveImageToPhotosAlbum({
            filePath: res.tempFilePath,
            success(res) {
              wx.showModal({
                title: '圖片儲存成功!',
                content: '請將圖片分享到朋友圈',
                showCancel: false,
                confirmText: '知道了',
                confirmColor: '#72B9C3',
                success: function (res) {
                  if (res.confirm) {
                    console.log('使用者點選確定');
                    that.setData({
                      hideshare:true
                    })
                  }
                }
              })
            }
          })
        },
        fail: function (res) {
          console.log(res)
        }
      })
    }, 1000);
  },

    /**
     * 生命週期函式--監聽頁面載入
     */
    onLoad: function (e) {
        this.data.id = e.id
        this.getGoodsDetails()
    },

    /**
     * 生命週期函式--監聽頁面初次渲染完成
     */
    onReady: function () {

    },

    /**
     * 生命週期函式--監聽頁面顯示
     */
    onShow: function () {

    },

    /**
     * 生命週期函式--監聽頁面隱藏
     */
    onHide: function () {

    },

    /**
     * 生命週期函式--監聽頁面解除安裝
     */
    onUnload: function () {

    },

    /**
     * 頁面相關事件處理函式--監聽使用者下拉動作
     */
    onPullDownRefresh: function () {

    },

    /**
     * 頁面上拉觸底事件的處理函式
     */
    onReachBottom: function () {

    },

    /**
     * 使用者點選右上角分享
     */
    onShareAppMessage: function () {
        return {
            title: this.data.details.name,
        }
    },

    /**
     * 使用者點選右上角轉發到朋友圈
     */
    onShareTimeline: function () {
        return {
            title: this.data.details.name,
        }
    }
})