前言
微信小程序需要生成海报进行朋友圈分享,但是不同的手机会有问题,
然后首先是图片的问题
图片
在模拟器上没有报错,可是真机测试却什么也没画出来。 canvas.drawImage 是不支持网络图片的,只支持本地图片。
所以,任何的网络图片都需要先缓存到本地,(当然上线的时候需要把网络图片的前缀加入白名单里面)
再通过 drawImage 调用存储的本地资源进行绘制,
缓存可以通过 wx.getImageInfo 和 wx.downloadFile 实现
wx.getImageInfo({ src: \'https://i415454.jpg\', success: function (res) { console.log(res.width) console.log(res.path) } })
然后通过 draw 方法 的是 draw 方法是异步的,如果图片还没加载成功,有可能画出来的是空的,所以 draw 方法通常都会带有定时器这样的回调。
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() }, 800) } })
画布首先分为 矩形, 图片,文字,线这几种,
是结合了promise 来处理 ,
产生的图片 直接通过 previewImage 进入手机预览模式,预览模式的图片可以直接保存到本地
然后就是我的DEMO 先放2张图
这就是生成后的图片了
先写组件 canvasdrawer
js:
Component({ properties: { painting: { type: Object, value: { view: [] }, observer(newVal, oldVal) { if (!this.data.isPainting) { if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { if (newVal && newVal.width && newVal.height) { this.setData({ showCanvas: true, isPainting: true }) this.readyPigment() } } else { if (newVal && newVal.mode !== \'same\') { this.triggerEvent(\'getImage\', { errMsg: \'canvasdrawer:samme params\' }) } } } } } }, data: { showCanvas: false, width: 100, height: 100, tempFileList: [], isPainting: false }, ctx: null, cache: {}, ready() { wx.removeStorageSync(\'canvasdrawer_pic_cache\') this.cache = wx.getStorageSync(\'canvasdrawer_pic_cache\') || {} this.ctx = wx.createCanvasContext(\'canvasdrawer\', this) }, methods: { readyPigment() { const { width, height, views } = this.data.painting this.setData({ width, height }) const inter = setInterval(() => { if (this.ctx) { clearInterval(inter) this.ctx.clearActions() this.ctx.save() this.getImagesInfo(views) } }, 100) }, getImagesInfo(views) { const imageList = [] for (let i = 0; i < views.length; i++) { if (views[i].type === \'image\') { imageList.push(this.getImageInfo(views[i].url)) } } const loadTask = [] for (let i = 0; i < Math.ceil(imageList.length / 8); i++) { loadTask.push(new Promise((resolve, reject) => { Promise.all(imageList.splice(i * 8, 8)).then(res => { resolve(res) }).catch(res => { reject(res) }) })) } Promise.all(loadTask).then(res => { let tempFileList = [] for (let i = 0; i < res.length; i++) { tempFileList = tempFileList.concat(res[i]) } this.setData({ tempFileList }) this.startPainting() }) }, startPainting() { const { tempFileList, painting: { views } } = this.data 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) { wx.showModal({ title: \'提示\', content: \'当前微信版本过低,无法使用 measureText 功能,请升级到最新微信版本后重试。\' }) this.triggerEvent(\'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() }, 800) } }) }, drawImage(params) { this.ctx.save() const { url, top = 0, left = 0, width = 0, height = 0, borderRadius = 0, deg = 0 } = params if (borderRadius) { // 圓角 this.ctx.beginPath() this.ctx.arc(width / 2 + left, height / 2 + top, width / 2, 0, Math.PI * 2, false); this.ctx.clip() this.ctx.drawImage(url, left, top, width, height) } else if (deg !== 0) { this.ctx.translate(left + width / 2, top + height / 2) this.ctx.rotate(deg * Math.PI / 180) this.ctx.drawImage(url, -width / 2, -height / 2, width, height) } else { 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, radius = 0 } = params this.ctx.setFillStyle(background) this.ctx.fillRect(left, top, width, height) if (radius!=0){ this.ctx.beginPath() this.ctx.setFillStyle(background) this.ctx.setStrokeStyle(background); this.ctx.setLineJoin(\'round\'); //交点设置成圆角 this.ctx.setLineWidth(radius) ; this.ctx.strokeRect(width + radius / 2, height + radius / 2, width - radius, height - radius); this.ctx.fillRect(width + radius, height + radius, width - radius * 2, height - radius * 2); this.ctx.stroke(); this.ctx.closePath(); } 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) } } }) }, saveImageToLocal() { const { width, height } = this.data wx.canvasToTempFilePath({ x: 0, y: 0, width, height, canvasId: \'canvasdrawer\', complete: res => { if (res.errMsg === \'canvasToTempFilePath:ok\') { this.setData({ showCanvas: false, isPainting: false, tempFileList: [] }) this.triggerEvent(\'getImage\', { tempFilePath: res.tempFilePath, errMsg: \'canvasdrawer:ok\' }) } else { this.triggerEvent(\'getImage\', { errMsg: \'canvasdrawer:fail\' }) } } }, this) } } })
html :
<canvas canvas-id="canvasdrawer" style="width:{{width}}px;height:{{height}}px;" class="board" wx:if="{{showCanvas}}"></canvas>
css:
.board { position: fixed; top: 2000rpx; }
在页面中调用这个组件 canvasdrawer
html:
<canvasdrawer painting="{{painting}}" class="canvasdrawer" bind:getImage="eventGetImage"/>
js:
// 生成 eventDraw() { wx.showLoading({ title: \'绘制分享图片中\', mask: true }) this.setData({ painting: { width: 375, height: app.globalData.screenHeight, clear: true, views: [ { type: \'rect\', background: this.data.skin.theme_color, top: 0, left: 0, width: 375, radius:10, height: app.globalData.screenHeight, }, { type: \'image\', url: \'/images/common/posterBg.png\', // 背景 //https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531103986231.jpeg top: 0, left: 0, width: 375, height: 667 }, { type: \'image\', url: \'/images/common/avatar.png\', top: 274, left: 30, width: 45, borderRadius:45, height: 45 //头像 }, { type: \'text\', content: \'大帅比哈哈\', fontSize: 15, color: \'#333333\', textAlign: \'left\', top: 564/2, left: 162/2, bolder: false }, { type: \'text\', content: \'邀请你一起来享受优惠!\', fontSize: 13, color: this.data.skin.theme_color, textAlign: \'left\', top: 611/2, left: 162 / 2 }, { type: \'image\', url: this.data.shareActivity.topImage, //活动图片 top: 104, left: 30, width: 315, height: 159 }, { type: \'image\', url: \'/images/common/avatar.png\', top: (600 +179) /2, left: 60, width: 245/2, height: 245/2 }, { type: \'image\', url: this.data.fingerImage, // 指紋 \'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385433625.jpeg\' top: (615 + 179) / 2, left: 213, width: 215 / 2, height: 215 / 2, }, { type: \'text\', content: this.data.shareActivity.shareTitle, //\'正品MAC魅可口红礼盒生日唇膏小辣椒Chili西柚情人\', fontSize: 16, lineHeight: 21, color: \'#383549\', textAlign: \'left\', top: 336, left: 30, width: 310, MaxLineNumber: 2, breakWord: true, bolder: true }, { type: \'text\', content: \'长按图片识别二维码,立即参与活动~\', fontSize: 13, color: \'#999\', textAlign: \'left\', top: (879+176)/ 2 , left: 75, lineHeight: 20, MaxLineNumber: 2, breakWord: true, width: 209 } ] } }) }, //保存 eventGetImage(event) { let _this = this; wx.hideLoading() const { tempFilePath, errMsg } = event.detail if (errMsg === \'canvasdrawer:ok\') { this.setData({ shareImage: tempFilePath, }) wx.previewImage({ urls: [tempFilePath], success: function () { _this.setData({ isShareBtnDisabled: false, painting:{}, }) }, fail: function () { } }) } }
这要就可以啦 海报就兼容 苹果和安卓 手机 嘻嘻
还需努力