需求概要

电商项目中需要将自己小店的商品带上自己的小程序码生成海报,保存到本地,然后分享到万能的朋友圈,QQ空间,微博等等来广而告之...
如下图,三种海报格式轮播展示,左滑右滑切换到海报,点击下面保存图片按钮,将当前海报保存到手机相册

思路

  • 需要商品信息,用户信息以及小程序码。
  • 使用swiper组件展示海报,
  • 将海报通过wx.createCanvasContext绘制到画布canvas组件。
  • 使用canvasToTempFilePath 将canvas海报保存到本地临时文件路径;
  • 使用saveImageToPhotosAlbum将图片保存到本地相册
  • 根据swiper组件的current属性判断当前保存的海报

解决方案

按照思路逐步实现:

商品信息,用户信息以及小程序码

1.商品信息通过导航事件传递到海报页,在此我使用的是模拟数据;
2.用户信息通过本地存储wx.setStorageSync 到缓存。

    // index.js//事件处理函数navToShare: function () {// 模拟数据var data = {thumb_images: ['https://cbu01.alicdn.com/img/ibank/2018/544/692/8567296445_882293189.400x400.jpg','https://cbu01.alicdn.com/img/ibank/2018/971/643/8581346179_882293189.400x400.jpg','https://cbu01.alicdn.com/img/ibank/2018/184/392/8567293481_882293189.400x400.jpg'],name: '2018夏季新款镂空圆领蝙蝠短袖t恤女装韩版宽松棉小衫上衣批发潮',price: 198,}wx.navigateTo({url: '../poster/poster?data=' + encodeURIComponent(JSON.stringify(data))})},

3.在海报页面onLoad函数的参数中获取商品信息
4.在海报页面获取本地缓存中的用户信息wx.getStorageSync
5.因为canvas绘制图片不支持跨域图片,所以先使用getImageInfo将网络图片返回图片的本地路径,

// poster.jsonLoad: function(options) {var data = JSON.parse(decodeURIComponent(options.data));var userinfo;//  获取本地存储的用户头像和昵称 userinfo = wx.getStorageSync('userInfo');console.log('用户信息', userinfo)// 渲染页面this.setData({avatar_url: userinfo.avatarUrl,nickname: userinfo.nickName,thumb_images: data.thumb_images,pro_price: data.price,pro_name: data.name,})//  保存网络图片到本地   用于canvas绘制图片wx.getImageInfo({src: userinfo.avatarUrl,success: (res) => {tmpAvatarUrl = res.path;}});// 保存产品图到本地  用于canvas绘制图片var thumbs = data.thumb_images;tmpThumbs = []; // 先清空,再添加新的产品图thumbs.forEach((item, i) => {wx.getImageInfo({src: item,success: (res) => {tmpThumbs.push(res.path)}})});},

7.小程序码由后端生成,前端通过POST请求将data传入,返回小程序码url,使用 wx.getImageInfo保存到本地

// 封装后的POST方法wxRequest.postRequest(url, data).then(res => {if (res.data.error_code == 0) {// 保存小程序码到本地  用于canvas绘制图片wx.getImageInfo({src: res.data.qrcode,success: (result) => {this.setData({poster_qrcode: result.path})}});}})

使用swiper组件展示海报

在这个项目中我是将页面渲染和canvas绘制分开的,因为小程序单位rpx自动适配各种设备屏幕。而canvas绘制单位是px。我没有做px和rpx之间的计算,保存px单位固定大小的图片也不错。

<view class='poster_swiper'><swiper bindchange="shareChange" current="{{current}}" circular="{{circular}}" previous-margin="100rpx" next-margin="100rpx" class="swiper_share"><swiper-item class="swiper_item1">// 根据设计渲染页面</swiper-item><swiper-item class="swiper_item2" wx:if="{{thumb_images.length>1}}">// 根据设计渲染页面</swiper-item><swiper-item class="swiper_item3" wx:if="{{thumb_images.length>2}}">// 根据设计渲染页面</swiper-item></swiper></view>

这里要用到swiper的几个属性列出来

current Number 0 当前所在滑块的 index
circular Boolean false 是否采用衔接滑动
previous-margin String "0px" 前边距,可用于露出前一项的一小部分,接受 px 和 rpx 值 1.9.0
next-margin String "0px" 后边距,可用于露出后一项的一小部分,接受 px 和 rpx 值 1.9.0
bindchange EventHandle current 改变时会触发 change 事件,event.detail = {current: current, source: source}

将海报通过wx.createCanvasContext绘制到画布canvas组件。。

1.在wxml中添加canvas组件,设置canvas-id以便于wx.createCanvasContext绘制画布

<canvas class='canvas-poster' canvas-id='canvasposter'></canvas>

定义样式固定定位到可视区以外,不影响可视区展示。

.canvas-poster {position: fixed;width: 280px;height: 450px;top: 100%;left: 100%;overflow: hidden;
}

三种海报分别绘制,具体看注释

  /*一张产品图*/drawPosterOne: function() {var ctx = wx.createCanvasContext('canvasposter');// ctx.clearRect(0, 0, 280, 450);/* 绘制背景*/ctx.rect(0, 0, 280, 450);ctx.setFillStyle('white');ctx.fillRect(0, 0, 280, 450);/*绘制店名*/ctx.setFontSize(16);ctx.setFillStyle('#333');ctx.textAlign = "center";ctx.fillText(this.data.nickname + '的小店', 140, 70);ctx.restore();/*绘制产品图*/ctx.drawImage(tmpThumbs[0], 35, 90, 210, 210);/* 绘制产品名称背景*/ctx.setFillStyle('#FF8409');ctx.fillRect(35, 300, 210, 60);/*绘制产品名称*/ctx.setFontSize(12);ctx.setFillStyle('#ffffff');ctx.textAlign = "left";ctx.fillText(this.data.pro_name.substr(0, 18), 45, 322);ctx.restore();ctx.setFontSize(12);ctx.setFillStyle('#ffffff');ctx.textAlign = "left";ctx.fillText(this.data.pro_name.substr(18, 20), 45, 344);ctx.restore();/* 绘制线框*/ctx.setLineDash([1, 3], 1);ctx.beginPath();ctx.moveTo(35, 375);ctx.lineTo(160, 375);ctx.moveTo(35, 435);ctx.lineTo(160, 435);ctx.setStrokeStyle('#979797');ctx.stroke();ctx.restore();/*绘制文字*/ctx.setFontSize(14);ctx.setFillStyle('#333333');ctx.textAlign = "left";ctx.fillText('¥', 35, 400);ctx.setFontSize(18);ctx.fillText(this.data.pro_price, 50, 400);ctx.setFontSize(11);ctx.setFillStyle('#666666');ctx.fillText(this.data.poster_qrtext, 35, 420);ctx.restore();/*绘制二维码*/ctx.drawImage(this.data.poster_qrcode, 185, 370, 60, 60);ctx.restore();/*圆形头像*/ctx.save()ctx.beginPath();ctx.arc(140, 30, 20, 0, 2 * Math.PI)ctx.setFillStyle('#fff')ctx.fill()ctx.clip()ctx.drawImage(tmpAvatarUrl, 120, 10, 40, 40)ctx.restore()ctx.draw(false, this.getTempFilePath);},/*两张产品图*/drawPosterTwo: function() {var ctx = wx.createCanvasContext('canvasposter');/* 绘制背景*/ctx.rect(0, 0, 280, 450);ctx.setFillStyle('white');ctx.fillRect(0, 0, 280, 450);/*绘制店名*/ctx.setFontSize(14);ctx.setFillStyle('#333');ctx.textAlign = "left";ctx.fillText(this.data.nickname + '的小店', 65, 36);ctx.restore();/* 绘制虚线框*/ctx.setLineDash([4, 1], 1);ctx.beginPath();ctx.moveTo(25, 60);ctx.lineTo(255, 60);ctx.moveTo(25, 325);ctx.lineTo(255, 325);ctx.setStrokeStyle('#979797');ctx.stroke();ctx.restore();/*绘制产品名称*/ctx.setFontSize(12);ctx.setFillStyle('#333');ctx.textAlign = "left";ctx.fillText(this.data.pro_name.substr(0, 13), 25, 82);ctx.setFontSize(12);ctx.setFillStyle('#333');ctx.fillText(this.data.pro_name.substr(13, 12) + '...', 25, 100);ctx.restore();/*绘制文字*/ctx.setFontSize(14);ctx.setFillStyle('#333333');ctx.textAlign = "left";ctx.fillText('¥', 190, 90);ctx.setFontSize(16);ctx.fillText(this.data.pro_price, 205, 90);ctx.restore();ctx.setFontSize(10);ctx.setFillStyle('#666666');ctx.textAlign = "center";ctx.fillText(this.data.poster_qrtext, 140, 420);ctx.restore();/*绘制产品图*/ctx.drawImage(tmpThumbs[0], 25, 115, 110, 150);ctx.drawImage(tmpThumbs[1], 145, 115, 110, 150);ctx.restore();/*绘制文字*/ctx.setFontSize(12);ctx.setFillStyle('#333333');ctx.textAlign = "left";ctx.fillText(this.data.slogan1, 25, 290);ctx.fillText(this.data.slogan2, 25, 308);ctx.restore();/*绘制二维码*/ctx.drawImage(this.data.poster_qrcode, 110, 330, 70, 70);ctx.restore();/*圆形头像*/ctx.save()ctx.beginPath();ctx.arc(35, 30, 20, 0, 2 * Math.PI)ctx.setFillStyle('#fff')ctx.fill()ctx.clip()ctx.drawImage(tmpAvatarUrl, 15, 10, 40, 40)ctx.restore()ctx.draw(false, this.getTempFilePath);},/*三张产品图*/drawPosterThree: function() {var ctx = wx.createCanvasContext('canvasposter');/* 绘制背景*/ctx.rect(0, 0, 280, 450);ctx.setFillStyle('white');ctx.fillRect(0, 0, 280, 450);/*绘制店名*/ctx.setFontSize(16);ctx.setFillStyle('#333');ctx.textAlign = "center";ctx.fillText(this.data.nickname + '的小店', 140, 70);ctx.restore();/* 绘制虚线框*/ctx.beginPath()ctx.setLineDash([4, 1], 1);ctx.beginPath();ctx.moveTo(20, 230);ctx.lineTo(145, 230);ctx.lineTo(145, 305);ctx.lineTo(40, 305);/*左下角圆角 ctx.arcTo( , 左下角左边坐标,左上角左边坐标,半径)*/ctx.arcTo(20, 305, 20, 230, 20);ctx.moveTo(20, 230);ctx.lineTo(20, 285);ctx.setStrokeStyle('#333333')ctx.stroke()ctx.setStrokeStyle('#979797');ctx.stroke();ctx.restore();/*绘制产品名称*/ctx.setFontSize(12);ctx.setFillStyle('#333');ctx.textAlign = "left";ctx.fillText(this.data.pro_name.substr(0, 9), 30, 250);ctx.setFontSize(12);ctx.setFillStyle('#333');ctx.fillText(this.data.pro_name.substr(9, 8) + '...', 30, 268);ctx.restore();/*绘制文字*/ctx.setFontSize(14);ctx.setFillStyle('#333333');ctx.textAlign = "left";ctx.fillText('¥', 30, 290);ctx.setFontSize(16);ctx.fillText(this.data.pro_price, 45, 290);ctx.restore();ctx.setFontSize(10);ctx.setFillStyle('#666666');ctx.textAlign = "center";ctx.fillText(this.data.poster_qrtext, 140, 420);ctx.restore();/*绘制产品图*/ctx.drawImage(tmpThumbs[0], 20, 90, 125, 125);ctx.drawImage(tmpThumbs[1], 160, 90, 100, 100);ctx.drawImage(tmpThumbs[2], 160, 205, 100, 100);ctx.restore();ctx.restore();/*绘制二维码*/ctx.drawImage(this.data.poster_qrcode, 110, 330, 70, 70);ctx.restore();/*圆形头像*/ctx.save()ctx.beginPath();ctx.arc(140, 30, 20, 0, 2 * Math.PI)ctx.setFillStyle('#fff')ctx.fill()ctx.clip()ctx.drawImage(tmpAvatarUrl, 120, 10, 40, 40)ctx.restore()ctx.draw(false, this.getTempFilePath);},

绘制中用到的数据如下

var tmpAvatarUrl = ""; /*用于绘制头像*/
var tmpThumbs = []; /*用于绘制产品图*/
var drawing = false; /*避免多次点击保存按钮*/
Page({/*** 页面的初始数据*/data: {circular: true, // swiper 是否采用衔接滑动current: 0, // swiper 当前所在滑块的 indexavatar_url: '', // 渲染头像nickname: '', // 渲染昵称poster_qrcode: '/images/poster_qrcode.png', // 小程序码poster_qrtext: '长按识别,即可查看商品',pro_name: '',  //产品名pro_price: '',  // 产品价格slogan1: '我的小店上新了,',  // 标语 1slogan2: '快来一起快来一起看看吧!', // 标语 2thumb_images: [] // 渲染图片},

使用canvasToTempFilePath 将canvas海报保存到本地临时文件路径;

  //获取临时路径getTempFilePath: function() {wx.canvasToTempFilePath({canvasId: 'canvasposter',success: (res) => {this.saveImageToPhotosAlbum(res.tempFilePath)}})},

使用saveImageToPhotosAlbum将图片保存到本地相册

  //保存至相册saveImageToPhotosAlbum: function(imgUrl) {if (imgUrl) {wx.saveImageToPhotosAlbum({filePath: imgUrl,success: (res) => {wx.showToast({title: '保存成功',icon: 'success',duration: 2000})drawing = false},fail: (err) => {wx.showToast({title: '保存失败',icon: 'none',duration: 2000})drawing = false}})}else{wx.showToast({title: '绘制中……',icon: 'loading',duration: 3000})}},

注意canvas绘制需要时间,所以设置 drawing 防止绘制被打断

根据swiper组件的current属性判断当前保存的海报

1.首先根据 change 事件设置current

 shareChange: function(e) {if (e.detail.source == 'touch') {this.setData({current: e.detail.current})}},

2.通过点击按钮执行savePoster保存海报到手机相册

  <view class="common_btn" catchtap="savePoster"><text>保存图片</text></view>

判断是否获取相册授权,已获得权限直接绘制,若未获得权限需提示用户前去设置授权

  /*保存海报到手机相册*/savePoster: function(e) {var that = this;var current = this.data.current;//获取相册授权wx.getSetting({success(res) {if (!res.authSetting['scope.writePhotosAlbum']) {wx.authorize({scope: 'scope.writePhotosAlbum',success() { //这里是用户同意授权后的回调that.drawPoster(current);},fail() { //这里是用户拒绝授权后的回调wx.showModal({title: '提示',content: '若不打开授权,则无法将图片保存在相册中!',showCancel: true,cancelText: '去授权',cancelColor: '#000000',confirmText: '暂不授权',confirmColor: '#3CC51F',success: function(res) {if (res) {wx.openSetting({//调起客户端小程序设置界面,返回用户设置的操作结果。})} else {// console.log('用户点击取消')}}})}})} else { //用户已经授权过了 that.drawPoster(current);}}})},

3.根据current判断当前海报绘制对应海报

  /* 绘制海报*/drawPoster: function(current) {if(drawing){wx.showToast({title: '绘制中……',icon: 'loading',duration: 3000}) }else{drawing = true;// loading // 根据swiper当前所在滑块的 index判断绘制对应海报switch (current) {case 0:this.drawPosterOne()break;case 1:this.drawPosterTwo()break;case 2:this.drawPosterThree()break;}}},

保存到手机相册的海报如下:

备注

图片来源于网络若有侵权请通知我立即删除
以上就是全部内容。不足之处请多指教!
完整案例

微信小程序之生成图片保存到相册 1相关推荐

  1. 微信小程序之生成图片保存到相册

    微信小程序之生成图片保存到相册 需求概要 电商项目中需要将自己小店的商品带上自己的小程序码生成海报,保存到本地,然后分享到万能的朋友圈,QQ空间,微博等等来广而告之- 如下图,三种海报格式轮播展示,左 ...

  2. 微信小程序制作海报保存到相册发朋友圈

    这个功能应该分三步来做: 一.制作海报图片 二.保存图片到相册 三.手动发朋友圈再到相册中取图片 详细步骤: 一.制作海报 1.要制作能保存到相册的图片,我们需要一个canvas标签,在我们的wxml ...

  3. 微信小程序之生成图片分享

    通过社交软件分享的方式来进行营销小程序,是一个常用的运营途径.小程序本身支持直接将一个小程序的链接卡片分享至微信好友或微信群,然后别人就可以通过点击该卡片进入该小程序页面.但是小程序目前不支持直接分享 ...

  4. 小程序权限设置:小程序下载图片保存到相册拒绝权限后,再次打开权限的解决方案

    小程序下载图片保存到相册功能,首次操作会提示:保存图片或视频到你的相册,有'拒绝'和'允许'两个选项,如果选择了拒绝就会保存失败:saveImageToPhotosAlbum:fail auth de ...

  5. 小程序canvas生成图片保存本地

    小程序canvas生成图片保存本地 注意事项:1.canvas中的图片最好使用 wx.getImageInfo提前下载下来, 2.canvas在处于视图隐藏时期无法生成图片 3.canvas图片生成需 ...

  6. 微信小程序下载网络图片保存到本地

    微信小程序下载网络图片保存到本地 问题背景 前一篇文章介绍了,微信小程序网络请求数据并在页面列表显示(参考 https://blog.51cto.com/baorant24/6189453 ),本文将 ...

  7. 微信小程序 webview 截图 保存相册

    此种有缺陷,在手机上截图会有缺失,建议使用微信小程序canvas重画一份, 类似:https://blog.csdn.net/qq_17407437/article/details/104892831 ...

  8. 小程序canvas绘图保存至相册

    基础知识点: 了解canvas基础知识 wx.createCanvasContext()//微信小程序创建画布 wx.canvasToTempFilePath()//将画布canvas转为图片 wx. ...

  9. 微信小程序之保存图片到手机相册设置白名单

    又到周五了,好开心,又能休息了!遇到的工作问题也应该清仓了. 微信小程序开发的时候,微信小程序不能分享朋友圈怎么办?这难不倒我们开发人员,可以生成小程序二维码分享到朋友圈! 上一次主要给大家讲解了怎么 ...

最新文章

  1. MongoDB 分页查询的方法及性能
  2. 添加打印机还显示脱机_打印机总是显示脱机无法打印的解决办法
  3. 追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (1)
  4. HBase+Spark技术双周刊 第四期
  5. jaba窗体连接mysql增删改查_知识实现——Java使用jdbc连接MySql数据库,实现增删改查...
  6. file_exists函数总是返回false
  7. Sql Server系列:日期和时间函数
  8. Postman 设置token为全局变量
  9. E 帮 SeSe 的一篇示例
  10. 基于 python 的股票和基金选取 程序设计
  11. oracle数据库自动修复,【案例】Oracle数据库由于存在坏块导致无法启动的恢复过程...
  12. Third season fifth episode,Phoebe‘s brother Frank came to see her
  13. CSDN2018博客之星评选结果预测第二弹
  14. 浙江移动彩信新sp接入指南
  15. 【微信开发6】专属推广二维码 java+SpringBoot
  16. linux是微内核还是宏内核,微内核与宏内核比较
  17. 华为路由器OSPF多区域配置
  18. 双系统 Win10下安装Linux(单/双硬盘)
  19. 为什么高防CDN将成为网站安全防护的必备?
  20. 有转正机会!阿里达摩院多模态理解组招收研究型实习生

热门文章

  1. 动态ip与静态ip的概念、区别、应用场景
  2. Ant X6 简单流程图运用
  3. hgame2020-week2-re
  4. 接口 抽象类 实体类
  5. eating的中文意思_eating怎么读什么意思
  6. 打印系统开发(7)——如何使用打印机
  7. 某数美验证码逆向分析
  8. #python装饰器(*****)
  9. 微信冷知识|那些朋友圈文字被折叠的原因所在
  10. 投影仪显示播放服务器连接异常,投影仪维修故障大全,这7条解决方案你一定用得到...