微信小程序上canvas绘制图片助手,一个json就制作分享朋友圈图片
前言
在开发小程序的过程中,你是否有将页面生成分享图并引导用户转发至朋友圈的需求?有,那就进来看看,应该是你想要的结果。
- 在分享上,就目前来说,小程序是不能直接分享到朋友圈的,微信官方只开放了分享到好友和群聊的 API。对于分享到朋友圈,现在大多数小程序所采用的方式,就是根据不同的需求生成一张带小程序码的图片引导用户分享至朋友圈。
- 当然分享图也可以在后台生成,但是你懂得,基本没有那个后台大哥愿意为你去用代码写一张图出来,所以,咱们还是自给自足。
效果图
引入组件,把canvas.wpy放到components目录下,painting 是需要传入的 json
//components/canvans.wpy <template> <canvas canvas-id="canvasdrawer" style="width:px; height:px;" class="board" wx:if=""> </canvas> </template> <script> import wepy from "wepy"; export default class Drawer extends wepy.component { /** * @argument * painting -- json数据 * @event * getImage 渲染图片完成后的回调 **/ props = { painting: { type: Object, default: {} } }; data = { showCanvas: false, width: 100, height: 100, index: 0, imageList: [], tempFileList: [], isPainting: false, ctx: null, cache: {} }; watch = { painting(newVal, oldVal) { if (!this.isPainting) { if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { if (newVal && newVal.width && newVal.height) { this.showCanvas = true; this.isPainting = true; this.$apply(); this.readyPigment(); } } else { if (newVal && newVal.mode !== "same") { this.$emit("getImage", { errMsg: "canvasdrawer:samme params" }); } } } } }; methods = {}; readyPigment() { const { width, height, views } = this.painting; this.width = width; this.height = height; const inter = setInterval(() => { if (this.ctx) { clearInterval(inter); this.ctx.clearActions(); this.ctx.save(); this.getImageList(views); this.downLoadImages(0); } }, 100); } getImageList(views) { const imageList = []; for (let i = 0; i < views.length; i++) { if (views[i].type === "image") { imageList.push(views[i].url); } } this.imageList = imageList; } downLoadImages(index) { const { imageList, tempFileList } = this; if (index < imageList.length) { this.getImageInfo(imageList[index]).then(file => { tempFileList.push(file); this.tempFileList = tempFileList; this.downLoadImages(index + 1); }); } else { this.startPainting(); } } startPainting() { const { tempFileList, painting: { views } } = this; for (let i = 0, imageIndex = 0; i < views.length; i++) { if (views[i].type === "image") { this.drawImage({ ...views[i], url: tempFileList[imageIndex] }); imageIndex++; } else if (views[i].type === "text") { if (!this.ctx.measureText) { wepy.showModal({ title: "提示", content: "当前微信版本过低,无法使用 measureText 功能,请升级到最新微信版本后重试。" }); this.$emit("getImage", { errMsg: "canvasdrawer:version too low" }); return; } else { this.drawText(views[i]); } } else if (views[i].type === "rect") { this.drawRect(views[i]); } } this.ctx.draw(false, () => { wx.setStorageSync('canvasdrawer_pic_cache', this.cache) const system = wx.getSystemInfoSync().system if (/ios/i.test(system)) { this.saveImageToLocal() } else { // 延迟保存图片,解决安卓生成图片错位bug。 setTimeout(() => { this.saveImageToLocal() }, 100); } }); } drawImage(params) { this.ctx.save(); const { url, top = 0, left = 0, width = 0, height = 0, radius = 0 } = params; if(radius != 0){ this.ctx.arc(left + radius, top + radius, radius, 0, 2 * Math.PI) this.ctx.clip(); } this.ctx.drawImage(url, left, top, width, height); this.ctx.restore(); } drawText(params) { this.ctx.save(); const { MaxLineNumber = 2, breakWord = false, color = "black", content = "", fontSize = 16, top = 0, left = 0, lineHeight = 20, textAlign = "left", width, bolder = false, textDecoration = "none" } = params; this.ctx.beginPath(); this.ctx.setTextBaseline("top"); this.ctx.setTextAlign(textAlign); this.ctx.setFillStyle(color); this.ctx.setFontSize(fontSize); if (!breakWord) { this.ctx.fillText(content, left, top); this.drawTextLine(left, top, textDecoration, color, fontSize, content); } else { let fillText = ""; let fillTop = top; let lineNum = 1; for (let i = 0; i < content.length; i++) { fillText += [content[i]]; if (this.ctx.measureText(fillText).width > width) { if (lineNum === MaxLineNumber) { if (i !== content.length) { fillText = `${fillText.substring(0, fillText.length - 1)}...`; this.ctx.fillText(fillText, left, fillTop); this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText); fillText = ""; break; } } this.ctx.fillText(fillText, left, fillTop); this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText); fillText = ""; fillTop += lineHeight; lineNum++; } } this.ctx.fillText(fillText, left, fillTop); this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText); } this.ctx.restore(); if (bolder) { this.drawText({ ...params, left: left + 0.3, top: top + 0.3, bolder: false, textDecoration: "none" }); } } drawTextLine(left, top, textDecoration, color, fontSize, content) { if (textDecoration === "underline") { this.drawRect({ background: color, top: top + fontSize * 1.2, left: left - 1, width: this.ctx.measureText(content).width + 3, height: 1 }); } else if (textDecoration === "line-through") { this.drawRect({ background: color, top: top + fontSize * 0.6, left: left - 1, width: this.ctx.measureText(content).width + 3, height: 1 }); } } drawRect(params) { this.ctx.save(); const { background, top = 0, left = 0, width = 0, height = 0 } = params; this.ctx.setFillStyle(background); this.ctx.fillRect(left, top, width, height); this.ctx.restore(); } getImageInfo(url) { return new Promise((resolve, reject) => { if (this.cache[url]) { resolve(this.cache[url]) } else { const objExp = new RegExp(/^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/) if (objExp.test(url)) { wx.getImageInfo({ src: url, complete: res => { if (res.errMsg === 'getImageInfo:ok') { this.cache[url] = res.path resolve(res.path) } else { this.triggerEvent('getImage', {errMsg: 'canvasdrawer:download fail'}) reject(new Error('getImageInfo fail')) } } }) } else { this.cache[url] = url resolve(url) } } }) } async saveImageToLocal() { const { width, height } = this; const res = await wepy.canvasToTempFilePath({ x: 0, y: 0, width, height, canvasId: "canvasdrawer" }, this); if (res.errMsg === "canvasToTempFilePath:ok") { this.showCanvas = false; this.isPainting = false; this.imageList = []; this.tempFileList = []; this.$apply(); this.$emit("getImage", { tempFilePath: res.tempFilePath, errMsg: "canvasdrawer:ok" }); } else { this.$emit("getImage", { errMsg: "canvasdrawer:fail" }); } } onLoad() { wepy.removeStorageSync("canvasdrawer_pic_cache"); this.cache = wepy.getStorageSync("canvasdrawer_pic_cache") || {}; this.ctx = wepy.createCanvasContext("canvasdrawer", this); } } </script> <style lang="less"> .board { position: fixed; top: 2000rpx; } </style>
在使用页面注册组件,代码如下,getImage 方法是绘图完成之后的回调函数,在 event.detail 中返回绘制完成的图片地址。
<view> <image src="" class="down"></image> <canvasdrawer :painting.sync="painting" @getImage.user="eventGetImage"></canvasdrawer> <button type='primary' class='openSetting' open-type="openSetting" wx:if="">去授权</button> <view class="longtap" wx:if="" @tap="eventSave">保存至手机相册</view> </view> import canvasdrawer from '@/components/canvas'; export default class Index extends wepy.page { components = { canvasdrawer, }; data = { shareImage: '',//生成的图片地址 painting: null,//canvas图片信息 show: false, userInfo: {},//用户信息 openSetBtn: false,//授权相册 }; watch = { shareImage: function(newVal) { if (newVal) { this.show = true; wx.hideLoading(); } } }; methods = { async eventSave() { // 保存图片至本地 let that = this; try { const res = await wepy.saveImageToPhotosAlbum({ filePath: this.shareImage }); that.openSetBtn = false; that.$apply(); if (res.errMsg === 'saveImageToPhotosAlbum:ok') { wepy.showToast({ title: '保存图片成功', icon: 'success', duration: 2000 }); } } catch (err) { console.log(err); if (err.errMsg != 'saveImageToPhotosAlbum:fail cancel') { wx.showModal({ title: '警告', content: '若不打开授权,则无法将图片保存在相册中!', showCancel: false }); that.openSetBtn = true; that.$apply(); } else { that.openSetBtn = false; that.$apply(); } } }, //生成图片 eventGetImage(event) { const { tempFilePath, errMsg } = event; if (errMsg === 'canvasdrawer:ok') this.shareImage = tempFilePath; }, }; getData(){ let that = this; let gameName = '游戏名字-5'; let num = 55; //礼包个数 let pre = 0; if (gameName) { gameName.split('').forEach((val, i) => { if (val.charCodeAt(0) > 127 || val.charCodeAt(0) == 94) { pre++; } else { pre += 0.5; } }); } this.painting = { width: 586, height: 742, views: [ { type: 'image', url: 'https://dl.gamdream.com/activity/douwan/20181108/share2.png', top: 0, left: 0, width: 586, height: 742 }, { type: 'image', url: 'https://dl.gamdream.com/activity/douwan/20181108/share2.png', top: 152, left: 222, width: 149, height: 149, radius: Math.floor(149 / 2) }, { type: 'image', url: 'https://dl.gamdream.com/activity/douwan/20181108/radius.png', top: 152, left: 222, width: 149, height: 149 }, { type: 'text', content: '微信名字', fontSize: 26, color: '#333333', textAlign: 'center', top: 307, left: 298 }, { type: 'text', content: '我在逗玩城已经领取', fontSize: 28, color: '#333333', textAlign: 'center', top: 350, left: 298 }, { type: 'text', content: num, fontSize: 48, color: '#333333', textAlign: 'left', top: 397, left: Math.floor((600 - String(num).length * 48 * 0.586 - 30) / 2) }, { type: 'text', content: '个', fontSize: 30, color: '#333333', textAlign: 'left', top: 412, left: Math.floor( (600 - String(num).length * 48 * 0.586 - 30) / 2 + String(num).length * 48 * 0.586 ) }, { type: 'text', content: gameName, fontSize: 30, color: '#8A2C1B', textAlign: 'left', top: 465, left: Math.floor((600 - (30 * pre + 20 + 60)) / 2) }, { type: 'text', content: '礼包', fontSize: 30, color: '#333333', textAlign: 'left', top: 465, left: Math.floor((600 - (30 * pre + 20 + 60)) / 2 + (30 * pre + 20)) }, ] }; that.$apply(); } onLoad(){ this.getData() } }
本文暂时没有评论,来添加一个吧(●'◡'●)