开发者社区 > 博文 > 使用taro+canvas实现微信小程序的图片分享功能
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

使用taro+canvas实现微信小程序的图片分享功能

  • 15****
  • 2023-05-17
  • IP归属:北京
  • 20960浏览


    业务场景

    二轮充电业务中,用户充电完成后在订单详情页展示订单相关信息,用户点击分享按钮唤起微信小程序分享菜单,将生成的图片海报分享给微信好友或者下载到本地,好友可通过扫描海报中的二维码加群领取优惠。


    使用场景及功能:微信小程序  生成海报图片 分享好友 下载图片

    使用技术: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的情况



    文章数
    1
    阅读量
    524

    作者其他文章