wxml
<view class="shareBox" style="backgound:{{isShow ? '#000' : '#fff'}}" wx:if="{{isShow && canvasList}}"> <canvas canvas-id="firstCanvas" class="canvas myCanvas" style="width:{{canvasWidth}}px;height:{{canvasHeight}}px;margin-top:calc((100vh - 54px - {{canvasHeight}}px) / 2)"></canvas> <button class="saveImg {{iphoneX ? 'iphonex' : ''}}" bindtap="saveImage" disabled="{{btnShow}}">保存图片</button> </view> <view wx:else class="refresh"> <button size="default" type="primary" bindtap="readyCanvas">重新加载</button> </view>
js
import regeneratorRuntime from "../../../lib/regenerator-runtime/runtime"; let ctx = false, crown = 0, widFit = 0 // ctx canvas对象, crown生成图的宽高比, widFit当然布局下与需生成图寛比, heiFit高度比 Component({ externalClasses:['myCanvas'], properties: { canvasList: { type: Array, value: [] }, getShareWidth: { //想得到的分享图宽度 type: Number, value: 1080 }, getShareHeight: { //想得到的分享图高度 type: Number, value: 1900 } }, /** * 组件的初始数据 */ data: { canvasWidth: 375, //屏幕宽度 canvasHeight: 375, //屏幕高度 isShow: true, //canvas组件默认显示 iphoneX: false, //适配机型 btnShow: false, //阻止事件多次触发 }, /** * 组件的方法列表 */ methods: { async canvasStart() { this.drawbackColor() 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 if(v.type === 'line'){ this.drawLine(v.drawLine) }else{ this.drawText(v.text,v.drawText) } }) ctx.draw() wx.hideLoading() // this.saveImage() }, //画默认白底色 drawbackColor(){ const { canvasWidth, canvasHeight } = this.data ctx.save() ctx.rect(0, 0, canvasWidth, canvasHeight) ctx.setFillStyle('white') ctx.fill() }, // 画背景图 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){ const canvasWidth = this.data.canvasWidth const exg = /[a-zA-Z0-9]/g const { x, y, bold, maxWidth = canvasWidth, fontSize = 14, lineHeight = 0, color = 'white', textAlign = 'left' } = drawText // text文字内容, x画布X坐标, y画布y坐标, maxWidth最大宽度, fontSize字体大小, color文字颜色 const patchTextHeight = fontSize ? fontSize : lineHeight if(!text || (!x && x !== 0) || (!y && y !== 0)){ wx.showToast({ title: '文字传入参数有误', icon: 'none' }) return } ctx.save() ctx.setFillStyle(color) //设置文字颜色 ctx.setTextAlign(textAlign) if(bold){ ctx.font = `${fontSize}px bold PingFangSC-Medium` //设置文字样式 }else{ ctx.setFontSize(fontSize) //设置文字字体 } const measure = ctx.measureText(text).width //测量文本宽度 const scale = Math.ceil(measure / (maxWidth * widFit)) //scale<1则 maxWidth>measure,1 <= scale < 2 则 maxWidth >= measure/2,scale >= 2 则 maxWidth <= measure / 2 let arr = [], count = 0 if(scale >= 2){ let fontNum = Math.floor((maxWidth / fontSize) * widFit) //每行最多字体个数 let patchVal = 0, patchY = y + patchTextHeight for(var i = 0; i < scale; i++){ arr[i] = text.substr(patchVal,fontNum) count = arr[i].match(exg) ? arr[i].match(exg).length / 2 - 1 : 0 // console.log('正则匹配:',ctx.measureText('和').width,ctx.measureText('B').width,ctx.measureText('a').width,ctx.measureText(1).width) fontNum += count if(i < scale - 1){ ctx.fillText(text.substr(patchVal,fontNum), x * widFit, patchY * widFit) patchVal += fontNum patchY += patchTextHeight }else{ arr[i] = text.substr(patchVal) ctx.fillText(text.substr(patchVal), x * widFit, patchY * widFit) //画最后剩下的内容 console.log('arr:',arr) } } }else{ ctx.fillText(text, x * widFit, (y + patchTextHeight)* widFit); console.log('felltext:',text,x*widFit,y*widFit) } ctx.restore() }, // 画线 drawLine(drwaLine){ const { sx, sy, ex, ey, lineWidth = 1, color = '#EBEBEB' } = drwaLine if(!sx || !sy || !ex || !ey){ wx.showToast({ title: '画线参数有误', icon: 'none' }) return }else{ ctx.save() ctx.moveTo(sx, sy) //线起点 ctx.lineTo(ex, ey) //线终点 ctx.setLineWidth(lineWidth) //线宽度 ctx.setStrokeStyle(color) //线颜色 ctx.stroke() ctx.restore() } }, // 保存图片 saveImage(){ wx.showLoading({ title: '生成图片中...', mask: true }) this.setData({ btnShow: true }) const { getShareWidth, getShareHeight } = this.data wx.canvasToTempFilePath({ destWidth: getShareWidth * 5, destHeight: getShareHeight * 5, canvasId: 'firstCanvas', quality: 1, complete: fin=>{ // console.log('finish',fin) if(fin.tempFilePath){ wx.saveImageToPhotosAlbum({ filePath: fin.tempFilePath, success: (res)=>{ wx.showToast({ title: '保存图片成功', icon: 'none', duration: 2000 }) this.setData({ btnShow: false }) } }) }else{ wx.showToast({ title: '生成图片失败', icon: 'none' }) this.setData({ btnShow: false, }) } } },this) }, //转成本地图片 getImages(url){ return new Promise( (sovle,reject)=>{ wx.getImageInfo({ src: url, success: (res)=>{ sovle(res.path) }, fail: (err)=>{ wx.showToast({ title: '网络不好,请稍后再试', icon: 'none' }) this.setData({ isShow: false }) } }); }) }, // 画图前准备工作 async readyCanvas(){ const { windowWidth, model } = wx.getSystemInfoSync(); // 获取屏幕宽高 let { getShareWidth, getShareHeight, canvasList} = this.data crown = getShareWidth / getShareHeight //分享图的宽高比 const canvasHeight = windowWidth / crown if(model === 'iPhone X'){ this.setData({ canvasWidth: windowWidth, canvasHeight, iphoneX: true }) }else{ this.setData({ canvasWidth: windowWidth, canvasHeight, iphoneX: false }) } 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) } } this.setData({ canvasList, isShow: true },()=>{ console.log(getShareWidth,getShareHeight,canvasList) this.canvasStart() }) } }, lifetimes: { }, pageLifetimes: { show(){ wx.showLoading({ title: '加载中...', mask: true }) this.readyCanvas() } } }) // let shareList = { //传参例子 // canvasList: [ // { // type: 'image', // clip: false, // url: courseInfo.bgImage, //顶部背景 // drawArguments:{ // dwX: 0, // dwY: 0, // dWidth: 375, // dHeight: 209 // }, // }, // { // type: 'text', // text: courseInfo.courseTitle, // drawText:{ // x: 20, // y: 227, // fontSize: 18, // color: '#303030', // bold: true // } // }, // { // type: 'image', // clip: true, // url: courseInfo.coachesInfo[0].avatar, //教练头像 // drawArguments:{ // dwX: 20, // dwY: 304, // dWidth: 32, // dHeight: 32 // }, // drawArc:{ // x: 36, // y: 320, // radius: 16, // } // }, // { // type:'line', // drawLine:{ // sx: 20, //开始x坐标 // sy: 348, //开始y坐标 // ex: 355, //结束x坐标 // ey: 348, //结束y坐标 // lineWidth: 0.5, //线宽度 // color: '#ebebeb' //线颜色 // } // }, // ], // getShareWidth: 375, // getShareHeight: 463, // pageTitle: courseInfo.courseTitle // }
wxss
.shareBox{ width: 100vw; height: 100vh; background: #000; position: fixed; left: 0; top: 0; } .canvas{ background: #fff; } .saveImg{ width: 100%; height: 108rpx; line-height: 108rpx; background: #00C3AA; text-align: center; font-size: 30rpx; color: #FFFFFF; border-radius: 0; border: 0; position: absolute; left: 0; bottom: 0; } .refresh{ width: 350rpx; height: 92rpx; position: fixed; left: 0; top: 0; right: 0; bottom: 0; margin: auto; } .iphonex{ height: 172rpx; }