在微信小程序使用canvas生成海报,并保存图片在本地
目标:在微信小程序中页面有一个按钮,点击后生成一张海报,点击保存,图片保存到本地相册
海报样式如下:
通过观摩别人代码,分析这张海报,难点有四个,一是背景的圆角,canvas并没有一个api是画圆角的,二是中间的两行标题,这里应该是动态的,可能一行可能两行,三是圆形头像处理,四是,画出的海报如何在点击下载的时候很好的布局。最终成果如下:
wxml:
<view class="btn" bindtap="share">点我生成海报</view> <canvas canvas-id="firstCanvas" class="canvas-exp"></canvas> <view hidden='{{previewHidden}}' class='preview'> <image src='{{preurl}}' mode='widthFix' class='previewImg' style="height:{{currentLineHeight}}px"></image> <button bindtap='eventSave' class="btnSave">保存到相册</button> </view>
wxss:
.btn { text-align: center; margin-top: 300rpx; } .btnSave { width: 408rpx; height: 92rpx; line-height: 92rpx; background: #FFFFFF; border-radius: 46rpx; text-align: center; font-size: 36rpx; font-weight: 600; color: #FF6C5D; margin-top:30rpx; } .preview{ width: 100%; height: 100%; background: rgba(0,0,0,0.7); position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10; } .canvas-exp { position: fixed; bottom: 0; right: 100%; width: 100%; height: 100%; background: transparent; } .previewImg { width: 560rpx; border-radius:56rpx; margin-top: 64rpx; margin-left:12.8%; }
js中的data:
data: { windowW: 0, windowH: 0, show: false, bgpic: '', propic: '', qCord: '', picImg: '', previewHidden: true, preurl: '', currentLineHeight:0 },
js中的onLoad:
onLoad: function (options) { let that = this // 获取设备宽高 wx.getSystemInfo({ success: function (res) { that.setData({ windowW: res.windowWidth, windowH: res.windowHeight }) } }) that.setData({ bgpic: '../../../assets/head.png', propic: '../../../assets/postbg.png', qCord: '../../../assets/code.png', picImg: '../../../assets/backImg.png' }) that.drawCanvas() }
在onLoad,海报中的图片资源应该是动态的可以在这请求好,在这边已经生成海报,考虑海报在点击以后再去生成要等待时间过长,图片资源下载失败还可以在页面加载之后再去请求。
canvas绘制海报函数:
drawCanvas() { let that = this let windowW = that.data.windowW let windowH = that.data.windowH let ctx = wx.createCanvasContext('firstCanvas') let text = '健康大使就发生了的打扫房间了放大设计方案放大镜双方就安分' let row = [] let strLen = text.length let rowNum = 0 // 计算文字行数 row.push(text.slice(0, 12)) rowNum = 1 if (strLen > 12 && (strLen <= 24)) { rowNum = 0 row.push(text.slice(12, 24)) } if (strLen > 24) { rowNum = 0 row.push(text.slice(12, 22) + '...') } ctx.drawImage(that.data.propic, (windowW - 279) / 2, 32, 279, (460 - rowNum * 26)) that.setData({ currentLineHeight: 460 - rowNum * 26 }) ctx.setFillStyle('#FFFFFF') // 白色圆角背景 let x = (windowW - 256) / 2 let y = 62 let r = 24 let w = 256 let h = 344 - rowNum * 26 ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5) ctx.moveTo(x + r, y) ctx.lineTo(x + w - r, y) ctx.lineTo(x + w, y + r) ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2) ctx.lineTo(x + w, y + h - r) ctx.lineTo(x + w - r, y + h) ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5) ctx.lineTo(x + r, y + h) ctx.lineTo(x, y + h - r) ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI) ctx.lineTo(x, y + r) ctx.lineTo(x + r, y) ctx.fill() // 识别小程序二维码 ctx.drawImage(that.data.qCord, (windowW - 236) / 2 + 173, 32 + 387, 60, 60) ctx.setFillStyle("#ffffff") ctx.setFontSize(13) ctx.fillText('长按识别二维码', (windowW - 236) / 2, 32 + 412 - rowNum * 26) ctx.setFillStyle("#ffffff") ctx.setFontSize(13) ctx.fillText('查看TA发布的全部精华内容', (windowW - 236) / 2, 32 + 433 - rowNum * 26) ctx.setFillStyle("#0B0D0E") ctx.setFontSize(15) ctx.fillText('健康医生', (windowW - 218) / 2 + 48, 32 + 345 - rowNum * 26) let collectImg = '../../../assets/hexagon.png' ctx.drawImage(collectImg, (windowW - 218) / 2, 92, 18, 20) ctx.font = 'normal bold 14px PingFang-SC-Medium'; ctx.setFillStyle("#AF8B43") ctx.fillText('精选内容', (windowW - 218) / 2 + 24, 108) ctx.drawImage(that.data.picImg, (windowW - 218) / 2, 32 + 160 - rowNum * 26, 213, 136) // 圆形头像 let avatarurl_width = 38 let avatarurl_heigth = 38 let avatarurl_x = (windowW - 218) / 2 let avatarurl_y = 352 - rowNum * 26 ctx.save() ctx.beginPath() ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false) ctx.clip() ctx.drawImage(that.data.bgpic, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth) ctx.restore() for (let b = 0; b < row.length; b++) { ctx.font = 'normal bold 22px PingFang-SC-Medium'; ctx.setFillStyle("#0B0D0E") ctx.fillText(row[b], (windowW - 218) / 2, 144 + b * 26, 218) } ctx.draw() },
由于标题数据动态,不知道是一行还是两行,如果超过两行只显示两行,且以省略号结尾,这会导致整张海报的高度发生改变,整个布局都会受影响,因而先计算标题行数。若先绘制标题,会被后面的白色背景覆盖,后文的文字也会带上标题的加粗等样式,所以放在了最后面。
点击生成海报,把海报转成图片显示在页面:
share: function () { var that = this wx.showLoading({ title: '努力生成中...' }) wx.canvasToTempFilePath({ x: (that.data.windowW - 279) / 2, y: 32, width: 279, height: that.data.currentLineHeight, canvasId: 'firstCanvas', fileType: 'jpg', quality: 1, success: function (res) { console.log(res.tempFilePath); that.setData({ preurl: res.tempFilePath, previewHidden: false, }) wx.hideLoading() }, fail: function (res) { console.log(res) } }) }
本来设计页面是点击生成海报,canvas跟保存海报按钮放在灰色背景上,但是在不同的设备下,保存海报按钮跟海报距离相差太大,因而改成页面加载,在可视范围之外把canvas画好,点击生成海报时,只是把海报转成图片,变成图片和按钮的布局
点击保存海报:
eventSave() { let that =this wx.saveImageToPhotosAlbum({ filePath: this.data.preurl, success(res) { wx.showToast({ title: '保存图片成功', icon: 'success', duration: 2000 }) that.setData({ previewHidden: true, }) } }) }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)