前言
微信小程序需要生成海报进行朋友圈分享,但是不同的手机会有问题,
然后首先是图片的问题
图片
在模拟器上没有报错,可是真机测试却什么也没画出来。 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, 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, 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, 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}}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: { 375, height: app.globalData.screenHeight, clear: true, views: [ { type: 'rect', background: this.data.skin.theme_color, top: 0, left: 0, 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, 375, height: 667 }, { type: 'image', url: '/images/common/avatar.png', top: 274, left: 30, 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, 315, height: 159 }, { type: 'image', url: '/images/common/avatar.png', top: (600 +179) /2, left: 60, 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, 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, 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, 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 () { } }) } }
这要就可以啦 海报就兼容 苹果和安卓 手机 嘻嘻
还需努力