这个问题我上网搜了一下,答案有多种,但是真正能用的没有几何。很多答案都是雷同,有的网友也不负责任,直接拿来照抄,自己也不跑一遍看看。哎,不说了,说多了全是泪。希望我们的技术达人在分享的时候,能够真实的走一遍代码,尽量能让我等小白看的懂啊。闹骚发过了,下面我们就进入正题吧(__) 嘻嘻……
今天分享的是不仅仅把页面生成图片,同时,也要满足能够识别图中的二维码。我们先来看看效果图
canvas组件
要想把页面生成图片,canvas是必不可少的,网上很多朋友说,页面生成的图片背景是空白,这个问题我遇到过,产生背景的原因是因为我们背景图片不能是本地地址,必须是网络地址,而且这个地址必须是https开头的,否则图片调用失败。由于我写的这个案例没有涉及到网络图片,所以我这个代码基本上没有出现问题。具体操作如下。
新建wxml页面
<canvas canvas-id=\'share\' style=\'width:375px;height:580px\' hidden=\'{{canvasHidden}}\'></canvas> <view class=\'container-main\'> <view class=\'am-title\'>点击图片预览后,长按或点击右上角保存分享</view> <view class=\'point-img\'> <image src=\'../../images/bj.png\' class=\'bjt\'></image> <view class="point-container"> <view class="am-img"><image src=\'{{imgurl}}\'></image></view> <view class=\'am-cname\'>姓名:<text>{{cname}}</text></view> <view class=\'am-yuyan\'>近期预言:<text>{{yuyan}}</text></view> <view class=\'am-renwu\'>人物匹配:<text>{{renwu}}</text></view> <view class=\'am-fan\'>前世烦恼:<text>{{fan}}</text></view> <view class=\'am-xg\'>人物性格:<text>{{xg}}</text></view> <view class="content"><image src=\'../../images/11.png\' class=\'seal\'></image> </view> </view> <view class=\'am-div\'></view> </view> </view> <view class=\'am-footer\'> <button open-type="share" data-tag="share1" id="btn_share1">转发</button> <button bindtap=\'saveImageToPhotosAlbum\'>保存图片</button> </view>
备注:在页面开头我们创建一下 canvas组件,因为我们生成图片是依赖它。
新建样式(wxss)
好的页面需要好的样式来搭配,下面是页面的样式,大家可以自行修改
.container-main {width:100%;} .am-title {background-color:#96c9ca; height:70rpx;line-height:70rpx; text-align: center; color:#ffffff; font-family: PingFangSC-Light,helvetica,\'Heiti SC\';font-size:.9rem;} .point-img { width:100%;height:100%;} .bjt {width:100%; height:85%; position: absolute; z-index: -1;} .am-title2 {font-size:1.6rem;padding-top:20px;padding-bottom:20px;font-weight:bold; text-align: center;color:#ffffff;} .seal {width:284px;height:80px;position: absolute;left:5%;} .point-container {top:20vh;text-align: left;font-size:1.0rem;border-radius: 10rpx; position: absolute; margin:10px;padding:10px;margin-top:0px;color:#5e7436;} .am-img {text-align: center;left:35vw;} .am-img image {width:200rpx;height:200rpx;} .point-container text {font-weight:bold;} .point-container .am-cname {padding-top:15rpx; padding-left:20rpx;padding-right:20rpx;} .point-container .am-yuyan {padding-top:15rpx; padding-left:20rpx;padding-right:20rpx;} .point-container .am-renwu {padding-top:15rpx;padding-left:20rpx;padding-right:20rpx;} .point-container .am-fan {padding-top:15rpx;padding-left:20rpx;padding-right:20rpx;} .point-container .am-xg {padding-top:15rpx;padding-left:20rpx;padding-right:20rpx;} .content {margin:15rpx auto; text-align: left;} .am-div {height:20rpx;} .am-footer {display:flex;height: 100rpx;width: 100%; text-align: center;position: absolute;bottom:-5px;left:0;} .am-footer button {border:#ff6a38 1px solid; border-radius:20rpx; width:300rpx; height: 70rpx; text-align: center; line-height:70rpx; font-size: 30rpx;background-color:#ffffff; letter-spacing:2px;color: #E97855;}
页面JS
这是关键部分,所有的实现效果就全靠它啦,具体如下:
const app = getApp(); /* 小程序利用canvas实现一键保存图片功能 https://blog.csdn.net/Charles_Tian/article/details/80910616 */ Page({ /** * 页面的初始数据 */ data: { cname: \'\', renwu: \'\', yuyan: \'\', fan: \'\', xg: \'\', imgurl: \'\', //人物头像 canvasHidden: true, //设置画板的显示与隐藏,画板不隐藏会影响页面正常显示 wxappName: \'页面生成图片\', //小程序名称 shareImgPath: \'\', screenWidth: \'\', //设备屏幕宽度 shareImgSrc: \'\', }, infoShow: function () { var that = this; wx.request({ url: app.globalData.api_url + \'rest/findProgram/36\', method: \'GET\', header: { \'Accept\': \'application/json\' }, success: function (res) { WxParse.wxParse(\'article_content\', \'html\', res.data.errMsg.s_remark, that, 5) //获取详细信息 res.data.content详细信息数据 that.setData({ info: res.data.errMsg // article_content: WxParse.wxParse(\'article_content\', \'html\', res.data.errMsg.n_remark, that, 5) //获取详细信息 res.data.content详细信息数据 }) wx.setStorageSync(\'cmsinfo\', res.data.errMsg) }, //接口调用失败的回调函数 fail: function (res) { var newData = wx.getStorageInfoSync(\'cmsinfo\') if (newData) { obj.setData({ info: newData }) } that.setData({ toastHidden: false, msg: \'当前网格异常,请稍后再试\' }) }, //口调用结束的回调函数(调用成功、失败都会执行) complete: function () { //显示出加载中的提示 that.setData({ loadHidden: true }) } }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { this.infoShow(); var that = this; wx.getImageInfo({ src: \'../../images/bj.png\', success: function (res) { console.log(res) that.setData({ shareImgSrc: \'../../\' + res.path }); } }) wx.getImageInfo({ src: \'../../images/11.png\', success: function (res) { console.log(res) that.setData({ shareImgPath: \'../../\' + res.path }); } }) //获取用户设备信息,屏幕宽度 wx.getSystemInfo({ success: res => { that.setData({ screenWidth: res.screenWidth }) console.log(that.data.screenWidth) } }) that.setData({ cname: options.id }); var yuyan = new Array(\'多注意外表,近期将有桃花运\', \'不要留钱在手,近期将走财运\', \'留意新认识的人,将遇改变人生的贵人\', \'走路留意,可能会走狗屎运\', \'放开吃喝,三个月内你不会发胖\', \'身边有人正在准备一个惊喜给你\'); var c = Math.floor(Math.random() * 6); var renwu = new Array(\'富察容音\', \'弘历\', \'魏璎珞\', \'娴妃\', \'高贵妃\', \'叶太医\', \'富察·傅恒\', \'海兰察\', \'陆晚晚\', \'明玉\'); var a = Math.floor(Math.random() * 6); var fan = new Array(\'因皇子去世一直耿耿于怀,内心苦闷。富察容音常有胸闷、乳房疼痛的症状,是情志失意导致的乳腺问题。\', \'前朝政务繁多,后宫妃嫔争宠,后宫女人几乎都有乳腺困扰,皇上命叶太医制出乳丹,帮助解决嫔妃的乳腺问题。\', \'长期想着复仇,情志不畅,所以月经紊乱,乳房有肿块都不知道。幸得叶太医乳丹相助,帮其解决乳腺问题。\', \'不能保家人周全,无奈黑化,也一直深受乳腺问题的烦恼。\', \'唯恐失去皇上宠爱,天天苦于宫斗,饱受乳腺问题烦恼。\', \'苦于没有疑难杂症给他医治,后被皇上指派解决嫔妃的乳腺问题,研发出乳丹。\', \'因为没有和心爱的女人在一起,从而抱恨终身。\', \'心爱的女人在成亲当天自杀,让海兰察心痛一生。\', \'自身实力与家庭背景都很薄弱,不求富贵,只求安保。\', \'因体内被纯贵妃扎的针重伤加之受顺嫔挑唆,以金剪插心,自尽而亡。\'); var xg = new Array(\'内心温柔,心胸宽广,最终别人,也能得到别人尊重。\', \'爱憎分明、内心骄傲,做人做事不会选择将就。\', \'客观理智、内心透彻,内心善良富有正义,重视承诺。\', \'追求单纯但是内心又自卑,遇到失望的事情,会变得偏执。\', \'喜形于色,为人处世情绪化,从不压抑内心想法。\', \'本是江湖游医却救阿哥、激皇上(治病)、怼太医,萌萌的外表下藏着一颗济世救人的医者仁心。\', \'深得皇上的宠爱和重视,表面上看是一个冷清的人,实际上是一个专情热血的男子。\', \'心性善良,智勇双全,屡经战阵,注重研习兵法,受众尊崇的将领。\', \'温婉贤淑的大家闺秀,才貌出众,天性善良,不争名利,清新脱俗。\', \'性格直爽,忠肝义胆,说话毒舌但是心地善良。\'); var img = new Array(\'http://login.pink333.com/uploads/20180828/4c9b626b59d6a0b38a03089d1da4d906.jpg\', \'http://login.pink333.com/uploads/20180828/d942b0f1bccc4e6c6081bd5f3398fd4e.jpg\', \'http://login.pink333.com/uploads/20180828/29af4184e336f82a38ecd3de07a01569.jpg\', \'http://login.pink333.com/uploads/20180828/eacea6d98f57fdcda835003fd8734c14.jpg\', \'http://login.pink333.com/uploads/20180828/9416f7ac87e99efd8a179dedec15d0c0.jpg\', \'http://login.pink333.com/uploads/20180828/1ee16bba3d950f8fce32d8e6d01025b3.jpg\', \'http://login.pink333.com/uploads/20180828/2114ed95f319b23afa498b6064b90517.jpg\', \'http://login.pink333.com/uploads/20180828/07682aa82440bd48bec9cfcdf8830fb0.jpg\', \'http://login.pink333.com/uploads/20180828/2f71cd25f32922035c08071042cda134.jpg\', \'http://login.pink333.com/uploads/20180828/ff6f46a6ad3c15200f1d380aa1ccde25.jpg\'); if (a == 0) { that.setData({ fan: fan[0], xg: xg[0], }); wx.getImageInfo({ src: \'../../images/fcry.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 1) { that.setData({ fan: fan[1], xg: xg[1] }); wx.getImageInfo({ src: \'../../images/hl.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 2) { that.setData({ fan: fan[2], xg: xg[2] }); wx.getImageInfo({ src: \'../../images/wyl.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 3) { that.setData({ fan: fan[3], xg: xg[3] }); wx.getImageInfo({ src: \'../../images/xf.jpg\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 4) { that.setData({ fan: fan[4], xg: xg[4] }); wx.getImageInfo({ src: \'../../images/ggf.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 5) { that.setData({ fan: fan[5], xg: xg[5] }); wx.getImageInfo({ src: \'../../images/yty.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 6) { that.setData({ fan: fan[6], xg: xg[6] }); wx.getImageInfo({ src: \'../../images/fcfh.jpg\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 7) { that.setData({ fan: fan[7], xg: xg[7] }); wx.getImageInfo({ src: \'../../images/hnc.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 8) { that.setData({ fan: fan[8], xg: xg[8] }); wx.getImageInfo({ src: \'../../images/lww.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } if (a == 9) { that.setData({ fan: fan[9], xg: xg[9] }); wx.getImageInfo({ src: \'../../images/my.png\', success: function (res) { console.log(res) that.setData({ imgurl: \'../../\' + res.path }); } }) } that.setData({ yuyan:yuyan[c], renwu: renwu[a] }); }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { var that = this; var context = wx.createCanvasContext(\'share\') context.setStrokeStyle("#00ff00") context.setLineWidth(1) context.stroke() context.draw(false, this.getTempFilePath) }, //获取临时路径 getTempFilePath: function () { wx.canvasToTempFilePath({ canvasId: \'share\', success: (res) => { this.setData({ shareTempFilePath: res.tempFilePath }) } }) }, /** * 绘制多行文本,考虑到内容过多可能会显示不全,这里增加一个函数 */ drawText: function (ctx, str, leftWidth, initHeight, titleHeight, canvasWidth) { var lineWidth = 0; var lastSubStrIndex = 0; //每次开始截取的字符串的索引 for (let i = 0; i < str.length; i++) { lineWidth += ctx.measureText(str[i]).width; if (lineWidth > canvasWidth) { ctx.fillText(str.substring(lastSubStrIndex, i), leftWidth, initHeight); //绘制截取部分 initHeight += 16; //16为字体的高度 lineWidth = 0; lastSubStrIndex = i; titleHeight += 30; } if (i == str.length - 1) { //绘制剩余部分 ctx.fillText(str.substring(lastSubStrIndex, i + 1), leftWidth, initHeight); } } // 标题border-bottom 线距顶部距离 titleHeight = titleHeight + 10; return titleHeight }, //保存至相册 saveImageToPhotosAlbum: function () { var that = this; var unit = that.data.screenWidth / 375 //2. canvas绘制文字和图片 const ctx = wx.createCanvasContext(\'share\'); var bgImgPath = that.data.shareImgSrc; var fanstr = \'前世烦恼:\' + that.data.fan; var rwxg = \'人物性格:\' + that.data.xg; //这里是把页面上的数据写入到画布里,具体的坐标需要各位自行调整 ctx.drawImage(bgImgPath, 0, 0, 375, 580); ctx.drawImage(that.data.shareImgPath, 50, 450, 284, 80); ctx.drawImage(that.data.imgurl, 146, 100, 100, 100); ctx.setFontSize(13) ctx.setFillStyle(\'#5e7436\') ctx.fillText(\'姓名:\' + that.data.cname, 50, 241) ctx.fillText(\'人物匹配:\' + that.data.renwu, 50, 271); ctx.fillText(\'近期预言:\' + that.data.yuyan, 50, 300); this.drawText(ctx, fanstr, 50, 330, 145, 280); this.drawText(ctx, rwxg, 50, 400, 145, 280); //ctx.fillText(\'前世烦恼:\' + that.data.fan, 50, 330); //ctx.fillText(\'人物性格:\' + that.data.xg, 50, 360); ctx.stroke() ctx.draw(false, function() { // 3. canvas画布转成图片 wx.canvasToTempFilePath({ x: 0, y: 0, width: 375, height: 580, destWidth: 375, destHeight: 580, canvasId: \'share\', success: function (res) { console.log(res); that.setData({ shareImgSrc: res.tempFilePath }) if (!res.tempFilePath) { wx.showModal({ title: \'提示\', content: \'图片绘制中,请稍后重试\', showCancel: false }) } //4. 当用户点击分享到朋友圈时,将图片保存到相册 wx.saveImageToPhotosAlbum({ filePath: that.data.shareImgSrc, success(res) { console.log(res); wx.showModal({ title: \'图片保存成功\', content: \'图片成功保存到相册了,去发圈噻~\', showCancel: false, confirmText: \'好哒\', confirmColor: \'#72B9C3\', success: function (res) { if (res.confirm) { console.log(\'用户点击确定\'); } that.setData({ canvasHidden: true }) } }) } }) }, fail: function (res) { console.log(res) } }) }); }, /** * 生命周期函数--监听页面显示 */ onShow: function () { const that = this; wx.downloadFile({ url: that.data.shareImgSrc, success: function (res) { that.data.shareImgSrc = res.tempFilePath }, fail: function (res) { } }) }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
通过以上方式我们就能轻松的把页面转换成图片。希望本次教程对你有所帮助。如果有不明白的地方,可以扫描下面的二维码加入我们的群,我会在群里给予指导,由于案例中出现很多图片,具体的内容可以到群里下载。