完善版封装canvas分享组件
import regeneratorRuntime from "../../../lib/regenerator-runtime/runtime"; let ctx = false, crown = 0, widFit = 0 // ctx canvas对象, crown生成图的宽高比, widFit当然布局下与需生成图寛比, heiFit高度比 Component({ /** * 组件的属性列表 */ properties: { canvasList: { type: Array, value: [ { type: 'backImage', url: 'https://portal.lanrenyun.cn/activity/50/activity_50-background-poster.jpg', }, { type: 'text', text: '一二三四五六七八九十十一十二十三十四十五', drawText:{ x: 100, y: 100, maxWidth: 200, fontSize: 14, lineHeight:60, color: '#000' } }, { type: 'image', clip: true, url: 'https://portal.lanrenyun.cn/Fs8toIXayCVeQAfLZDhsdlG-kcWX', drawArguments:{ dwX: 153, dwY: 660, dWidth: 106, dHeight: 106 }, drawArc:{ x: 206, y: 713, radius: 53, } }, { type: 'image', clip: true, url: 'https://portal.lanrenyun.cn/Fs8toIXayCVeQAfLZDhsdlG-kcWX', drawArguments:{ dwX: 435, dwY: 1497, dWidth: 210, dHeight: 210 }, drawArc:{ x: 540, y: 1602, radius: 105, } }, ] }, getShareWidth:{ //想得到的分享图宽度 type: Number, value: 1080 }, getShareHeight:{ //想得到的分享图高度 type: Number, value: 1900 } }, /** * 组件的初始数据 */ data: { canvasWidth: 375, //屏幕宽度 canvasHeight: 375, //屏幕高度 isShow: true, //canvas组件默认显示 }, /** * 组件的方法列表 */ methods: { async canvasStart() { const canvasList = this.data.canvasList await canvasList.map( (v,k)=>{ if(v.type === 'backImage'){ this.drawBackImg(v.url) }else if(v.type === 'image'){ this.drawContentImg(v) }else{ this.drawText(v.text,v.drawText) } }) ctx.draw() this.saveImage() }, // 画背景图 drawBackImg(url){ // console.log('drawBackImg',url) const { canvasWidth, canvasHeight } = this.data ctx.save() ctx.drawImage(url, 0, 0, canvasWidth, canvasHeight) }, // 画图不用裁剪(查看小程序canvas api 文档 https://developers.weixin.qq.com/miniprogram/dev/api/CanvasContext.drawImage.html) drawImg(url, drawArguments) { const arg = Object.keys(drawArguments) if (arg.length == 8) { const { sx, sy, sWidth, sHeight, dwX, dwY, dWidth, dHeight } = drawArguments ctx.drawImage(url, sx * widFit, sy * widFit, sWidth * widFit, sHeight * widFit, dwX * widFit, dwY * widFit, dWidth * widFit, dHeight * widFit) } else if (arg.length == 4) { const { dwX, dwY, dWidth, dHeight } = drawArguments ctx.drawImage(url, dwX * widFit, dwY * widFit, dWidth * widFit, dHeight * widFit) } else if (arg.length == 2) { const { dwX, dwY } = drawArguments ctx.drawImage(url, dwX * widFit, dwY * widFit) } else { wx.showToast({ title: '背景图传入参数有误,请确认无误后再进行操作', icon: 'none' }) } }, // 画内容图 clip裁剪 drawContentImg(val){ const { url, clip, drawArguments, drawArc} = val if(clip){ //裁剪流图片 if(!url || !drawArguments || !drawArc){ wx.showToast({ title: '请确认drawContentImg参数无误', icon: 'none' }) return; } const { x, y, radius } = drawArc if((x || x === 0 ) && (y || y === 0) && (radius || radius === 0)){ const { x, y, radius, startRadian = 0 , endRadian = 2 * Math.PI } = drawArc ctx.save() ctx.beginPath(); ctx.arc(x * widFit, y * widFit, radius * widFit, startRadian, endRadian) // arc(x坐标,y坐标,radius半径,startRadian起始弧度/单位弧度(默认在3点钟方向),endRadian终止弧度) ctx.clip() this.drawImg(url, drawArguments) ctx.restore() }else{ wx.showToast({ title: '画圆参数有误', icon: 'none' }) } }else{ ctx.save() this.drawImg(url, drawArguments) ctx.restore() } }, // 画文字 drawText(text,drawText){ debugger const { x, y, maxWidth, fontSize, lineHeight = 0, color = 'white' } = drawText // text文字内容, x画布X坐标, y画布y坐标, max最大宽度, fontSize字体大小, color文字颜色 if(!text || (!x && x !== 0) || (!y && y !== 0) || !maxWidth || !fontSize){ wx.showToast({ title: '文字传入参数有误', icon: 'none' }) return } ctx.save() ctx.setFontSize(fontSize) //设置文字字体 const measure = ctx.measureText(text).width //测量文本宽度 const scale = Math.ceil(measure / maxWidth) //scale<1则 maxWidth>measure,1 <= scale < 2 则 maxWidth >= measure/2,scale >= 2 则 maxWidth <= measure / 2 let arr = [] if(scale >= 2){ const fontNum = Math.floor(maxWidth / fontSize) //每行最多字体个数 let patchVal = 0, patchY = y for(var i = 0; i < scale; i++){ arr[i] = text.substr(patchVal,fontNum) if(i < scale - 1){ ctx.fillText(text.substr(patchVal,fontNum), x * widFit, patchY * widFit) patchVal += fontNum patchY += (fontSize + lineHeight * widFit) }else{ arr[i] = text.substr(patchVal) ctx.fillText(text.substr(patchVal), x * widFit, patchY * widFit) //画最后剩下的内容 console.log('arr:',arr) } } }else if(scale == 1 && maxWidth != measure ){ const fontNum = Math.floor(maxWidth / fontSize) //每行最多字体个数 ctx.fillText(text.substr(0,fontNum), x * widFit, y * widFit) ctx.fillText(text.substr(fontNum), x * widFit, (y+fontSize) * widFit) // console.log(text,arr,measure) }else{ ctx.fillText(text, x * widFit, y * widFit); } ctx.setFillStyle(color) ctx.restore() }, // 保存图片 saveImage(){ const { getShareWidth, getShareHeight } = this.data wx.canvasToTempFilePath({ destWidth: getShareWidth, destHeight: getShareHeight, canvasId: 'firstCanvas', quality: 1, complete(fin){ console.log('finish',fin) if(fin.tempFilePath){ wx.hideLoading(); wx.saveImageToPhotosAlbum({ filePath: fin.tempFilePath, success: (result)=>{ wx.hideLoading() wx.showToast({ title: '保存图片成功', icon: 'none' }) } }) }else{ wx.showToast({ title: '生成图片失败', icon: 'none' }) } } },this) }, //转成本地图片 getImages(url){ return new Promise( (sovle,reject)=>{ wx.getImageInfo({ src: url, success: (res)=>{ sovle(res.path) }, fail: (err)=>{ reject(err) } }); }) } }, lifetimes: { async attached() { wx.showLoading({ title: '生成图片中...', mask: true }) // 获取屏幕宽高 const { windowWidth } = wx.getSystemInfoSync(); let { getShareWidth, getShareHeight, canvasList} = this.data crown = getShareWidth / getShareHeight //分享图的宽高比 const canvasHeight = windowWidth / crown this.setData({ canvasWidth: windowWidth, canvasHeight }) ctx = wx.createCanvasContext('firstCanvas',this) //把ctx赋值给全局变量 widFit = windowWidth / getShareWidth //宽比 (以px为单位) for(let i = 0; i < canvasList.length; i++){ if(canvasList[i].url){ canvasList[i].url = await this.getImages(canvasList[i].url) console.log(i,canvasList[i].url) } } const self = this console.log(4,this.data.canvasList) // await this.getImageInfo() //网络图片转成本地图片或临时图片 this.setData({ canvasList },function(){ self.canvasStart() }) }, } })
最重要的是要注意换算比例以及自定义组件this参数,draw(true,this), 生存图片的时候也要带上this参数
wx.canvasToTempFilePath(Object object, Object this)