由於我們無法將小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,業界目前的做法是利用小程序的 Canvas 功能生成一張帶有小程序碼的圖片,然后引導用戶下載圖片到本地后再分享到朋友圈。相信大家在繪制分享圖中應該踩到 Canvas 的各種彩蛋(坑)了吧~
這里首先推薦一個開源的組件:painter(通過該組件目前我們已經成功在支付寶小程序上也應用上了分享圖功能)
咱們不多說,直接上手就是干。最終效果如下

首先我們新增一個自定義組件,在該組件的json中引入painter
{
"component": true,
"usingComponents": {
"painter": "/painter/painter"
}
}
然后組件的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" />
接着組件的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: { 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']) { wx.showModal({ title: '提示', content: '您未開啟保存圖片到相冊的權限,請點擊確定去開啟權限!', success(res) { if (res.confirm) { wx.openSetting() } } }) } } }) setTimeout(() => { wx.hideLoading() this.setData({ visible: false }) }, 300) } }) } } })
如此一個繪制分享圖的自定義組件就完成啦。
效果圖如下:

tips:
- 暫不支持繪制圖片 圓角為:10rpx 0rpx 0rpx 0rpx 類似的。
- 文字居中實現可以看下我代碼片段
- 文字換行實現(maxLines)只需要設置寬度,maxLines如果設置為1,那么一行超出將會展示為省略號
當然如果想支持四個圓角的 試試 把Pen.js文件78行的_doClip方法重寫,代碼:
_doClip(borderRadius, width, height) { if (borderRadius && width && height) { let border = borderRadius.split(' ') let r1 = 0 let r2 = 0 let r3 = 0 let r4 = 0 if (border.length==1){ r1 = r2 = r3 = r4 = Math.min(border[0].toPx(), width / 2, height / 2); }else{ r1 = Math.min(border[0] == 0 ? 0 : border[0].toPx(), width / 2, height / 2); r2 = Math.min(border[1] == 0 ? 0 : border[1].toPx(), width / 2, height / 2); r3 = Math.min(border[2] == 0 ? 0 : border[2].toPx(), width / 2, height / 2); r4 = Math.min(border[3] == 0 ? 0 : border[3].toPx(), width / 2, height / 2); } //const r = Math.min(borderRadius.toPx(), width / 2, height / 2); // 防止在某些機型上周邊有黑框現象,此處如果直接設置 setFillStyle 為透明,在 Android 機型上會導致被裁減的圖片也變為透明, iOS 和 IDE 上不會 // setGlobalAlpha 在 1.9.90 起支持,低版本下無效,但把 setFillStyle 設為了 white,相對默認的 black 要好點 this.ctx.setGlobalAlpha(0); this.ctx.setFillStyle('white'); this.ctx.beginPath(); this.ctx.arc(-width / 2 + r1, -height / 2 + r1, r1, 1 * Math.PI, 1.5 * Math.PI); this.ctx.lineTo(width / 2 - r2, -height / 2); this.ctx.arc(width / 2 - r2, -height / 2 + r2, r2, 1.5 * Math.PI, 2 * Math.PI); this.ctx.lineTo(width / 2, height / 2 - r3); this.ctx.arc(width / 2 - r3, height / 2 - r3, r3, 0, 0.5 * Math.PI); this.ctx.lineTo(-width / 2 + r4, height / 2); this.ctx.arc(-width / 2 + r4, height / 2 - r4, r4, 0.5 * Math.PI, 1 * Math.PI); this.ctx.closePath(); this.ctx.fill(); // 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此類型上的 clip,也就意味着,在此版本微信的 ios 設備下無法使用 border 屬性 if (!(getApp().systemInfo && getApp().systemInfo.version <= '6.6.6' && getApp().systemInfo.platform === 'ios')) { this.ctx.clip(); } this.ctx.setGlobalAlpha(1); } }
主要是把borderRadius屬性Split為多個變量,然后一個一個判斷有沒有值。
使用方法(要么只給一個值,要么四個值都給):
鏈接:https://www.jianshu.com/p/3a76d719409b