使用taro+canvas實現微信小程式的圖片分享功能

京東雲開發者發表於2023-05-18

業務場景

二輪充電業務中,使用者充電完成後在訂單詳情頁展示訂單相關資訊,使用者點選分享按鈕喚起微信小程式分享選單,將生成的圖片海報分享給微信好友或者下載到本地,好友可透過掃描海報中的二維碼加群領取優惠。

使用場景及功能:微信小程式 生成海報圖片 分享好友 下載圖片

使用技術:Taro vue vant canvas

實現效果圖

重點步驟拆分

1、封裝一個海報分享元件 poster-share.vue

2、用canvas畫圖,將背景圖、費用、二維碼等資訊繪製在一張圖上,其中費用、二維碼是動態獲取的

3、生成一張本地快取圖片

4、喚起微信分享功能,實現分享和下載功能

重點步驟有了,那麼就開幹吧!

核心程式碼實現

1、模版部分

需要一個畫布dom用來繪製圖片,一個用來存放生成圖片的dom

問:canvasId為什麼需要動態生成呢?

答:避免一個頁面中使用多個元件引起的canvasId重複問題

<template>
  <div class="poster-share__content">
    <!-- canvas生成的海報圖片 -->
    <img
      v-if="posterImg"
      class="poster-share__content--img"
      mode="aspectFit"
      :src="posterImg"
    >
    <!-- 分享海報canvas繪製部分 -->
    <canvas
      class="poster-share__content--cvs"
      :canvas-id="canvasId"
    ></canvas>
  </div>
</template>

2、樣式部分

該業務場景下,不能讓使用者看到畫布,但是設定canvas的display為none將不能進行繪製,會報如下錯誤,導致繪製失敗。

實現方式:採用定位的方式,將canvas定位到可視區域外,具體程式碼如下。

.poster-share__content {
  position: absolute;
  right: -9999px;
  top: -9999px;
  width: 560px;
  height: 852px;
  opacity: 0;
  z-index: -1;

  &--img {
    width: 100%;
    height: 100%;
  }

  &--cvs {
    width: 100%;
    height: 100%;
  }
}



3、核心js部分

開始寫核心實現啦~

父元件傳參控制子元件是否開始繪製,子元件繪製完成後通知父元件改變狀態。

  name: 'CpPosterShare',
  model: {
    prop: 'value',
    event: 'update:value',
  },
  props: {
    value: {
      type: Boolean,
      default: false,
    },
    config: {
      type: Object,
      default: () => ({}),
    },
  },
  data () {
    return {
      isDraw: false, // 是否開始繪製海報
      posterImg: '', // 生成的海報圖片地址
      canvasId: `canvasId${ Math.random() }`,
      screenWidth: null, // 螢幕寬度
    }
  },
  watch: {
    value: {
      handler (val) {
        this.isDraw = val
      },
      immediate: true,
    },
    isDraw (val) {
      this.$emit('update:value', val)
      if (val) {
        this.init()
      }
    },
  },

首先,我們做的是一個小程式,將圖片放在小程式原始碼中會加大包的體積,需要從網路上下載圖片,因此需要封裝一個公共的方法來獲取圖片的資訊。Taro提供getImageInfo方法返回圖片的原始寬高、本地路徑等資訊。

// 載入圖片
loadImg (src) {
  return newPromise((resolve, reject) => {
    Taro.getImageInfo({
      src,
    }).then((res) => {
      resolve({ ...res })
    }).catch((err) => {
      reject(err)
    })
  })
}



該業務場景中涉及繪製多張圖片,包括背景圖片和二維碼圖片,需要將多張圖片都load完成後才能開始繪製。

const promiseParams = [this.loadImg(BgImage), this.loadImg(QRcode)]
const promiseAll = Promise.all(promiseParams.map((item) =>item.catch(() =>null)))

promiseAll.then((res) => {
  this.draw(res)
}).catch((err) => {
  console.log(err)
})

開始繪製啦~

建立canvas繪圖上下文CanvasContext物件,呼叫Taro提供的方法Taro.createCanvasContext(canvasId)繪製背景圖、繪製價格、繪製二維碼,這裡就不一一贅述了。全部繪製完成後,將畫布中的內容匯出生成圖片,Taro提供了canvasToTempFilePath方法,需要在draw()回撥中呼叫才能保證圖片匯出成功,返回生成圖片的臨時路徑。

ctx.draw(false, () => {
  Taro.canvasToTempFilePath({
    canvasId:this.canvasId,
  }).then((res) => {
    this.posterImg = res.tempFilePath

    // 喚起分享選單
    this.showShareImageMenu()
  }).catch((err) => {
    console.log('海報生成失敗', err)
    Taro.showToast({
      title: '海報生成失敗',
      icon: 'error',
    })
  }).finally(() => {
    Taro.hideLoading()
    this.isDraw = false
  })
})

本地圖片生成成功後,喚起微信提供的分享選單彈窗,可以將圖片傳送給朋友、收藏、儲存到相簿。Taro提供showShareImageMenu方法喚起分享選單彈窗,入參為本地圖片路徑。

showShareImageMenu () {
  if (Taro.showShareImageMenu) {
    Taro.showShareImageMenu({
      path:this.posterImg,
    }).then().catch((err) => {
      console.log(err)
      const { errMsg } = err
      // 取消操作  errMsg === 'showShareImageMenu:fail cancel'
      // 拒絕授權  errMsg: "showShareImageMenu:fail auth deny"
      if (errMsg === 'showShareImageMenu:fail auth deny') {
        authorize({
          scope:'writePhotosAlbum',
          showModal:true,
          authName:'儲存圖片到相簿',
          success: () => {
            this.downloadImg()
          },
        })
      }
    }).finally(() => {
      this.isDraw = false
    })
  } else {
    Taro.showToast({
      title:'小程式版本不支援該功能',
      icon:'error',
    })
  }
}



使用者點選傳送給朋友,會調起微信對話方塊,將生成的海報圖片貼上分享給朋友;

點選收藏,會將海報圖片新增到收藏列表中,方便下次檢視;

點選儲存到相簿,會喚起儲存圖片授權彈窗,使用者點選允許,會將海報圖片儲存在本地相簿中。

如果使用者在儲存圖片授權彈窗中第一次點選拒絕,之後再次點選分享下載時,需要有授權提示彈窗,提示使用者是否開啟設定去授權,具體展示如下。

Taro提供Taro.openSetting方法調起小程式設定頁面,使用者開啟“新增到相簿”授權後成功後,呼叫Taro提供的下載圖片的方法Taro.saveImageToPhotosAlbum將圖片下載到本地。

授權提示彈窗 設定頁面 下載提示

其中判斷使用者是否開啟授權的方法具體實現如下:

/**
 * 許可權獲取流程
 * @param scope       許可權英文名稱
 * @param success     授權成功的回撥
 * @param fail        授權失敗的回撥
 * @param showModal   授權失敗是否展示對話方塊提示
 * @param authName    授權失敗是否展示對話方塊提示展示的授權名稱
 *  // 例子:開啟使用者的相簿許可權
    authorize({
      scope: 'writePhotosAlbum',
      showModal: true,
      authName: '儲存圖片到相簿',
      success () {
        console.log('授權成功')
      },
    })
*/
export async function authorize (options) {
  const {
    scope, success, fail, showModal = false, authName = '',
  } = options
  try {
    const scopeName = `scope.${ scope }`
    const auth = await Taro.getSetting()
    if (!auth.authSetting[scopeName]) {
      Taro.authorize({ scope: scopeName }).then((res) => {
        if (res.errMsg === 'authorize:ok' && success) success()
      }, () => {
        if (showModal && authName) {
          Taro.showModal({
            title: '授權提示',
            content: `您拒絕了${ authName }許可權,是否開啟設定去授權?`,
          }).then((res) => {
            if (res.confirm) {
              Taro.openSetting().then((res2) => {
                if (res2.authSetting[scopeName] && success) {
                  success()
                } else if (fail) {
                  fail()
                }
              })
            } else {
              fail && fail()
            }
          }).catch((res) => {
            fail && fail(res)
          })
        } else {
          fail && fail()
        }
      })
    } else {
      success && success()
    }
  } catch (err) {
    fail && fail(err)
  }
}

至此,具體實現完結撒花~ 可以將元件用到頁面中了

元件引用

<poster-share
  v-model="draw"  // 是否開始繪製海報海報
  config="config"  // 海報配置資訊
/>



問題記錄

在開發過程中遇到了一些問題,記錄一下

現象:點選分享,生成canvas圖片。開發者工具上每次都正常,ios機每次都正常,部分安卓機每次都正常,部分安卓機,點選分享之後取消,操作多次,有幾次會生成圖片失敗

報錯資訊:"errMsg": "canvasToTempFilePath:fail :create bitmap failed"

錯誤定位解決:canvas需要一直顯示,不能有display:none的情況

作者:京東零售 張夢雨

內容來源:京東雲開發者社群

相關文章