评论

如何实现快速生成分享图的功能

如何快速生成一张分享图~

由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种彩蛋(坑)了吧~

这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能)

咱们不多说,直接上手就是干。

首先我们新增一个自定义组件,在该组件的json中引入painter (dialog-modal为:如果用户拒绝保存分享图,弹窗提示引导用户在设置内开启保存图片权限)

{
  "component": true,
  "usingComponents": {
    "painter": "/painter/painter",
    "dialog-modal": "/components/dialogModal/index"
  }
}

然后组件的WXML (代码片段在最后)

<view class="share-wrap" wx:if="{{visible}}" catchtouchmove="preventDefault">
  <view class="share-back"></view>
  <view class="share-container">
    <view class="close" bindtap="handleClose"></view>
    <image mode="widthFix" src="{{sharePath}}" class="share-image" />
    <view class="share-tips">保存图片,叫伙伴们来参与吧</view>
    <view class="save-btn" bindtap="handlePhotoSaved"></view>
  </view>
</view>
<painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" />
<dialog-modal isShow="{{isModal}}" title="提示" content="您未开启保存图片到相册的权限,请点击确定去开启权限!" confirmType="openSetting" />

接着组件的WXSS (代码片段在最后)

.share-wrap {
  width: 100%;
}

.share-back {
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 888;
}

.share-container {
  width: 100%;
  background: #FFF;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 999;
}

.close {
  width: 30rpx;
  height: 30rpx;
  overflow: hidden;
  position: absolute;
  right: 64rpx;
  top: 64rpx;
}

.close::after {
  transform: rotate(-45deg);
}

.close::before {
  transform: rotate(45deg);
}

.close::before,
.close::after {
  content: '';
  position: absolute;
  height: 3rpx;
  width: 100%;
  top: 50%;
  left: 0;
  margin-top: -2rpx;
  background: #9C9C9C;
}

.share-image {
  width: 420rpx;
  margin: 66rpx auto 0;
  display: block;
  border-radius: 16rpx;
  box-shadow: 0px 4rpx 8px 0px rgba(0, 0, 0, 0.1);
}

.share-tips {
  width: 100%;
  text-align: center;
  color: #3C3C3C;
  font-size: 28rpx;
  margin: 32rpx 0;
}

.save-btn {
  width: 336rpx;
  height: 96rpx;
  margin: 0 auto 94rpx;
  background: url('https://qiniu-image.qtshe.com/20190506save-btn.png') center center;
  background-size: 100% 100%;
}

重点来了 JS (代码片段在最后)

Component({
  properties: {
    //属性值可以在组件使用时指定
    isCanDraw: {
      type: Boolean,
      value: false,
      observer(newVal, oldVal) {
        newVal && this.drawPic()
      }
    }
  },
  data: {
    isModal: false, //是否显示拒绝保存图片后的弹窗
    imgDraw: {}, //绘制图片的大对象
    sharePath: '', //生成的分享图
    visible: false
  },
  methods: {
    handlePhotoSaved() {
      this.savePhoto(this.data.sharePath)
    },
    handleClose() {
      this.setData({
        visible: false
      })
    },
    drawPic() {
      if (this.data.sharePath) { //如果已经绘制过了本地保存有图片不需要重新绘制
        this.setData({
          visible: true
        })
        this.triggerEvent('initData') 
        return
      }
      wx.showLoading({
        title: '生成中'
      })
      this.setData({
        imgDraw: {
          width: '750rpx',
          height: '1334rpx',
          background: 'https://qiniu-image.qtshe.com/20190506share-bg.png',
          views: [
            {
              type: 'image',
              url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg',
              css: {
                top: '32rpx',
                left: '30rpx',
                right: '32rpx',
                width: '688rpx',
                height: '420rpx',
                borderRadius: '16rpx'
              },
            },
            {
              type: 'image',
              url: wx.getStorageSync('avatarUrl') || 'https://qiniu-image.qtshe.com/default-avatar20170707.png',
              css: {
                top: '404rpx',
                left: '328rpx',
                width: '96rpx',
                height: '96rpx',
                borderWidth: '6rpx',
                borderColor: '#FFF',
                borderRadius: '96rpx'
              }
            },
            {
              type: 'text',
              text: wx.getStorageSync('nickName') || '青团子',
              css: {
                top: '532rpx',
                fontSize: '28rpx',
                left: '375rpx',
                align: 'center',
                color: '#3c3c3c'
              }
            },
            {
              type: 'text',
              text: `邀请您参与助力活动`,
              css: {
                top: '576rpx',
                left: '375rpx',
                align: 'center',
                fontSize: '28rpx',
                color: '#3c3c3c'
              }
            },
            {
              type: 'text',
              text: `宇宙最萌蓝牙耳机测评员`,
              css: {
                top: '644rpx',
                left: '375rpx',
                align: 'center',
                fontWeight: 'bold',
                fontSize: '44rpx',
                color: '#3c3c3c'
              }
            },
            {
              type: 'image',
              url: 'https://qiniu-image.qtshe.com/20190605index.jpg',
              css: {
                top: '834rpx',
                left: '470rpx',
                width: '200rpx',
                height: '200rpx'
              }
            }
          ]
        }
      })
    },
    onImgErr(e) {
      wx.hideLoading()
      wx.showToast({
        title: '生成分享图失败,请刷新页面重试'
      })
    },
    onImgOK(e) {
      wx.hideLoading()
      this.setData({
        sharePath: e.detail.path,
        visible: true,
      })
      //通知外部绘制完成,重置isCanDraw为false
      this.triggerEvent('initData') 
    },
    preventDefault() { },
    // 保存图片
    savePhoto(path) {
      wx.showLoading({
        title: '正在保存...',
        mask: true
      })
      wx.saveImageToPhotosAlbum({
        filePath: path,
        success: (res) => {
          wx.showToast({
            title: '保存成功',
            icon: 'none'
          })
          setTimeout(() => {
            this.setData({
              visible: false
            })
          }, 300)
        },
        fail: (res) => {
          wx.getSetting({
            success: res => {
              let authSetting = res.authSetting
              if (!authSetting['scope.writePhotosAlbum']) {
                this.setData({
                  isModal: true
                })
              }
            }
          })
          setTimeout(() => {
            wx.hideLoading()
            this.setData({
              visible: false
            })
          }, 300)
        }
      })
    }
  }
})

如此一个绘制分享图的自定义组件就完成啦。(dialogModal的内容已写到代码片段内)

效果图如下:

tips:

  • 暂不支持绘制图片 圆角为:10rpx 0rpx 0rpx 0rpx 类似的。
  • 文字居中实现可以看下我代码片段
  • 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么一行超出将会展示为省略号

gitHub地址:https://github.com/Kujiale-Mobile/Painter

有什么不懂的可下方评论。

代码片段:https://developers.weixin.qq.com/s/jiEZu4m17z9b

因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名。

最后一次编辑于  07-31  (未经腾讯允许,不得转载)
点赞 31
收藏
评论

14 个评论

  • 精彩极了
    精彩极了
    11-08

    不好意思,刚发现问题就删掉了评论发表

    11-08
    赞同 1
    回复 9
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      11-08
      额?如果不行的话 建议用网络图片试试
      11-08
      1
      回复
    • 精彩极了
      精彩极了
      11-08回复仙森ღ₅₂₀¹³¹⁴
      本地图片可以加载绘制进来,就是有一点错误提示,不太友好,哈哈
      11-08
      1
      回复
    • 精彩极了
      精彩极了
      11-11回复仙森ღ₅₂₀¹³¹⁴
      用painter连续加载了三次,生成了三张图片,生成时会闪屏,然后左右滑动时也会闪屏,求解啊大佬
      11-11
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      11-11
      canvas绘制图,性能本来就差点。你这个连续绘制3次。建议你写三个painter的内容。写三个canvas
      11-11
      1
      回复
    • 精彩极了
      精彩极了
      星期二 10:06回复仙森ღ₅₂₀¹³¹⁴
      这样生成图片之后,把临时路径放到swiper里面去,就一直闪一直闪,部分手机是正常的,小米的就狂闪,不知道为什么
      星期二 10:06
      1
      回复
    查看更多(4)
  • 猿猿猿ಠ_ರೃ树林
    猿猿猿ಠ_ರೃ树林
    10-22

    大佬可以帮我看下代码嘛 真机测试没反应,开发工具可以正常展示的


    https://developers.weixin.qq.com/s/hZWdfimQ7acv

    10-22
    赞同
    回复 8
    查看更多(3)
  • wait
    wait
    10-16

    我想设置背景透明。好像不支持透明,然后我就想矩形不填充颜色,然后就有一个默认黑色的了

    10-16
    赞同
    回复 3
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-16
      透明背景 用一张png的图
      10-16
      回复
    • wait
      wait
      10-16回复仙森ღ₅₂₀¹³¹⁴
      感谢,一时没想起来。还有个问题就是变量该怎么传值呢
      10-16
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-17回复wait
      用`` 这个符号。。比如 `职位${job.jobName}`
      10-17
      回复
  • L°
    10-16

    只能生成一屏的图片呀,超过就自动给截掉了

    10-16
    赞同
    回复 7
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-16
      你设置 view的高度就行了。我设置死了 1334.你可以随便设置的。
      10-16
      回复
    • L°
      10-16回复仙森ღ₅₂₀¹³¹⁴
      设置了  开发者工具可以完整保存,手机不行,我的iphone6  你可以设置一个高度大于1334的,我试过了  就用那个painter git里面得demo
      10-16
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-16回复
      实测iPhone X上正常。
      10-16
      回复
    • L°
      10-17回复仙森ღ₅₂₀¹³¹⁴
      昨天忘记说了,真机不行,开发工具正常
      10-17
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-17回复
      我是真机iPhone X测试正常
      10-17
      回复
    查看更多(2)
  • print_r()
    print_r()
    10-10

    你好,如果我的头像是这个链接

    https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epkMHjdesoQib2lVEVKYuDhDfnl5K7S57FPI8btpqdCUtibrEoSQZibavUjgwFibqKaVSXe1GibU8482Yw/132

    是不是不可以用的?是不是得闲下载到本地用wx.downloadFile?


    10-10
    赞同
    回复 9
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-10
      不用,需要在后台加下官方的这个downloadfiile域名,建议你头像放自己的cdn上,然后 存到缓存里面。这个地方从缓存取,避免用户更换头像后该链接失效,。
      10-10
      回复
    • print_r()
      print_r()
      10-10回复仙森ღ₅₂₀¹³¹⁴
      ok。多谢
      10-10
      回复
    • print_r()
      print_r()
      10-10回复仙森ღ₅₂₀¹³¹⁴
      这个js里面可以写类似 onload onshow方法吗?我想让画布自适应铺满屏幕
      10-10
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      10-10回复print_r()
      可以。组件内有单独的onShow方法
      10-10
      回复
    • print_r()
      print_r()
      10-10回复仙森ღ₅₂₀¹³¹⁴
      ok
      10-10
      回复
    查看更多(4)
  • 你好耀眼。
    你好耀眼。
    09-17


    css设置了line-through会偏移一段距离

    09-17
    赞同
    回复 2
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      09-18
      是 align-center 和 left引起的。你调试下
      09-18
      回复
    • 你好耀眼。
      你好耀眼。
      09-18回复仙森ღ₅₂₀¹³¹⁴
      但是价格它可能是区间,可能是单个价格,所以这个align-center不可以去掉吧
      09-18
      回复
  • leeyoung
    leeyoung
    09-12

    imgOK 绘制完了为啥没执行这里执行了,但是我的页面里面没回调
    未执行onImgOK 请问我哪儿搞错了吗

    09-12
    赞同
    回复 6
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      09-12
      代码写错了。能写个代码片段给我么
      09-12
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      09-12
      看下 你 wxml上 怎么写的
      09-12
      回复
    • leeyoung
      leeyoung
      09-12回复仙森ღ₅₂₀¹³¹⁴

      mpvue

      09-12
      回复
    • leeyoung
      leeyoung
      09-12回复仙森ღ₅₂₀¹³¹⁴

      图片也绘制出来了,就是没imgOK没执行,我是在组件里面绘制的,不是路由页面,请问有影响吗,因为usingComponents只能在路由里面才有效,但是canvas的绘制在组件里面



      09-12
      回复
    • leeyoung
      leeyoung
      09-12回复仙森ღ₅₂₀¹³¹⁴
      我用其他办法拿到了imgPath
      09-12
      回复
    查看更多(1)
  • 玉米的小跟班
    玉米的小跟班
    08-26

    大佬,这个能传参数吗,比如另外一人扫码,自动活动的参数

    08-26
    赞同
    回复 1
  • 侯衍超
    侯衍超
    08-16

    频率很高

    08-16
    赞同
    回复 3
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      08-19
      https://developers.weixin.qq.com/s/nJxTNJmG7QaN 图片建议压缩后再让后端返回 可接入https://tinypng.com/
      08-19
      回复
    • 侯衍超
      侯衍超
      08-19回复仙森ღ₅₂₀¹³¹⁴
      目前的解决方案是使用小程序的压缩接口压缩一遍,但是偶尔还是会出现绘制失败的情况
      08-19
      回复
    • 仙森ღ₅₂₀¹³¹⁴
      仙森ღ₅₂₀¹³¹⁴
      08-19回复侯衍超
      我一直未复现。是安卓机型上出现的吗?
      08-19
      回复
  • 侯衍超
    侯衍超
    08-16

    有1-3张图片需要渲染到海报图上,但是偶然情况下,图片会渲染失败。海报图上留下一个大大的空白,无法显示图片。

    报错如下:


    08-16
    赞同
    回复 4

正在加载...