小程序通过canvas生成海报保存为图片的技巧

最近公司要求在小程序点击分享,要生成一张图片,可以保存在用户相册里,图片里的内容根据后台返回的数据生成,这就涉及到小程序画布的知识了,因为微信文档上,画布的Api很多已经废弃了,而且新的Api怎么用也没写的很清楚,所以就踩了很多坑。经过我查阅了大量大佬写过的文章,自己也总结一些方法,避免后面的人踩坑。

声明canvas的组件

<canvas disable-scroll="true" type="2d" id="myCanvas" canvas-id="card" style="width: 700rpx;height: 1100rpx;"></canvas>
<!--声明一个宽度为700,高度为1100的画布-->
<!--这里disable-scroll为true是为了防止画布在滚动的时候,发生穿透,导致底部内容也会跟着滚动,具体根据大家各自业务考虑是否使用-->

这里注意,属性id和属性canvas-id是有区别的,前者用于样式或者生成canvas实例,后者是调用微信api接口时需要用到,这个看后面的例子就会明白

由于我想在页面打开的时候,就开始绘制画布,因为这样在显示画布的时候内容就已经差不多渲染好了。注意如果这样做就不能做隐藏处理,因为做了隐藏处理,生成canvas实例就会报错,原因是找不到canvas这个节点。除非你是显示画布后才开始绘制画布,但是个人觉得这样会让用户体验下降,特别是绘制网络图片的时候,通常还要等个一两秒图片才绘制好。
那么,如果想要页面打开后就开始绘制画布,又不让用户察觉到画布的存在,该怎么做呢?我自己的解决方案是通过定位,将画布向左移到200%,超出可视窗口,这样用户既察觉不到画布的存在,生成canvas实例时又可以找到canvas这个节点。代码下图所示

 <canvas disable-scroll="true" type="2d" id="myCanvas" canvas-id="card" style="width: 700rpx;height: 1100rpx;left:{{canvasLeft}}"><cover-image src="../../utils/image/card/button.png" class="btnArea" catchtap="handleSave">保存图片</cover-image></canvas>
<!--因为我想用一张图片覆盖在画布上,由于canvas是原生组件,层级最高,能覆盖他的只有cover-image和cover-view了-->
data: {canvasLeft: "200%"
},
//想要显示画布就把canvasLeft设置为50%即可
#myCanvas {border-radius: 20rpx;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);
}

下图所示是新 Canvas 2D 实例的代码,还在用wx.createCanvasContext的小伙伴请停止使用,因为官方已经废除了。(注意,使用微信开发工具的真机调试画布会报错,这是微信开发工具的bug,如果要看画布在真机上的效果,请用预览的功能

 draw() {const query = wx.createSelectorQuery() // 创建节点查询器 queryquery.select('#myCanvas')//获取canvas节点,这里是id不是canvas-id.fields({ node: true, size: true }).exec((res) => {const canvas = res[0].nodethis.data.canvasNode = canvas//保存canvas实例const ctx = canvas.getContext('2d')const dpr = wx.getSystemInfoSync().pixelRatio //获取设备像素比canvas.width = res[0].width * dpr//设置画布的宽度canvas.height = res[0].height * dpr//设置画布的高度ctx.scale(dpr, dpr)//在调用后,之后创建的路径其横纵坐标会被缩放,这里建议设计图统一以750px的为准,这样像素就不用换算,可以直接写入//<--这里是绘制画布内容的代码-->})},

绘制画布内容

绘制文本
//绘制字体大小为24px(12px),颜色为#999的文本
const rpx = wx.getSystemInfoSync().windowWidth / 750 //这里是为了内容能在不同设备自适应
ctx.font = `${24 * rpx}px sans-serif`;//设置当前字体样式
ctx.fillStyle = '#999999'//设置填充色,这里建议颜色不要缩写
ctx.fillText('佣金比例:', 269 * rpx, 252 * rpx);//在画布上绘制被填充的文本,ctx.fillText(要绘制的文本, x坐标, y坐标)//绘制字体大小为34px(17px),颜色为红色,粗体的文本
const rpx = wx.getSystemInfoSync().windowWidth / 750 //这里是为了内容能在不同设备自适应
ctx.font = `bold ${34 * rpx}px sans-serif`;
ctx.fillStyle = 'red'
ctx.fillText('佣金比例:', 379 * rpx, 210 * rpx);//绘制自动换行文本(这里借鉴的是别人的写法,是哪个大佬写的我忘了,因为大佬用的是旧版的写法和自己的业务需要的原因,所以自己改了点代码)
//这里我写在util.js里面,方便调用。
let dealWords = function (options, rpx) {options.ctx.fillStyle = options.fontColor//设置字体颜色options.ctx.font = options.fontSize;//设置字体大小var allRow = Math.ceil(options.ctx.measureText(options.word).width / options.maxWidth);//实际总共能分多少行var count = allRow >= options.maxLine ? options.maxLine : allRow;//实际能分多少行与设置的最大显示行数比,谁小就用谁做循环次数var endPos = 0;//当前字符串的截断点for (var j = 0; j < count; j++) {var nowStr = options.word.slice(endPos);//当前剩余的字符串var rowWid = 0;//每一行当前宽度    if (options.ctx.measureText(nowStr).width > options.maxWidth) {//如果当前的字符串宽度大于最大宽度,然后开始截取for (var m = 0; m < nowStr.length; m++) {rowWid += options.ctx.measureText(nowStr[m]).width;//当前字符串总宽度if (rowWid > options.maxWidth) {if (j === options.maxLine - 1) { //如果是最后一行options.ctx.fillText(nowStr.slice(0, m - 1) + '...', options.x, options.y + (j + 1) * options.lineheight * rpx);    //(j+1)*18这是每一行的高度        } else {options.ctx.fillText(nowStr.slice(0, m), options.x, options.y + (j + 1) * options.lineheight * rpx);}endPos += m;//下次截断点break;}}} else {//如果当前的字符串宽度小于最大宽度就直接输出options.ctx.fillText(nowStr.slice(0), options.x, options.y + (j + 1) * options.lineheight * rpx);}}
}
module.exports = {dealWords
}//调用的方法
const utils = require('../../utils/util.js')
const rpx = wx.getSystemInfoSync().windowWidth / 750 //这里是为了内容能在不同设备自适应
utils.dealWords({ctx: ctx, //画布上下文fontSize: `${30 * rpx}px sans-serif`, //设置当前字体样式fontColor: '#333333',//设置填充色word: '哈哈哈哈', //需要处理的文字lineheight: 40,//行高的配置maxWidth: 384 * rpx, //一行文字最大宽度x: 270 * rpx, //文字在x轴要显示的位置y: 39 * rpx, //文字在y轴要显示的位置maxLine: 3 //文字最多显示的行数
}, rpx)
绘制图片
//绘制图片的封装方法,支持生成普通图片、水印图片(通过设置透明度)、圆形图片(常用于小程序二维码一物一码的场景下)
let drawImage = function (canvas, ctx, imgsrc, x, y, width, height, isAlpha,globalAlpha,isRange) {return new Promise((resolve, reject) => {let img = canvas.createImage()img.src = imgsrcimg.onload = function () {ctx.save();//保存绘图上下文。与ctx.restore()配对//是否绘制透明度图片if (isAlpha) {ctx.globalAlpha = globalAlpha //设置透明度。范围 0-1,0 表示完全透明,1 表示完全不透明} else {ctx.globalAlpha = "1"}//是否绘制圆形图片if (isRange) { //开始创建一个路径。ctx.beginPath();//创建一条弧线。创建一个圆可以指定起始弧度为 0,终止弧度为 2 * Math.PI。//ctx.arc(圆心的x坐标,圆心的y坐标,圆的半径,起始弧度,终止弧度,弧度的方向是否是逆时针)ctx.arc(width / 2 + x, height / 2 + y, width / 2, 0, Math.PI * 2, false);//从原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。//可以在使用 clip 方法前通过使用 save 方法对当前画布区域进行保存,并在以后的任意时间通过restore方法对其进行恢复。ctx.clip();}//绘制图像到画布,ctx.drawImage(图片实例, x坐标, y坐标, 宽度, 高度);//网络图片要通过 getImageInfo / downloadFile 先下载,代码后文会写ctx.drawImage(img, x, y, width, height);ctx.restore();//恢复之前保存的绘图上下文。与ctx.save()配对resolve()}img.onerror = (err) => {console.log(err)reject(err)}})
}
module.exports = {drawImage
}//调用的方法
const utils = require('../../utils/util.js')
const rpx = wx.getSystemInfoSync().windowWidth / 750 //这里是为了内容能在不同设备自适应
//绘制宽度为172px,高度为74px,透明度0.2的图片
utils.drawImage(canvas, ctx, '../../utils/image/card/logo.png', 489 * rpx, 71 * rpx, 172 * rpx, 74 * rpx, true,0.2,false)//绘制网路图片
//网络图片要通过wx.getImageInfo获取图片信息。网络图片需先去小程序平台配置download域名才能生效。
wx.getImageInfo({src: '此处为网路图片的路径',success: function (res) {//res.path 网络图片转为本地的临时路径//图片压缩,由于有些网络图片体积太大,影响绘图速度,所以可以适当的压缩下wx.compressImage({src: res.path, quality: 1, // 压缩质量,范围0~100,数值越小,质量越低,压缩率越高(仅对jpg有效)complete: function (res) {//res.tempFilePath是压缩后的路径//utils.drawImage为上文绘制图片的封装方法utils.drawImage(canvas, ctx, res.tempFilePath, 49 * rpx, 55 * rpx, 200 * rpx, 200 * rpx)}})}
})//在画布上生成微信小程序二维码,一物一码的业务场景下
//这里我们服务端调用的是wxacode.getUnlimited,接口会返回二维码的图片Buffer,我们需要做的是把图片buff转换成小程序本地路径
//要注意小程序请求服务端接口时,responseType要设置为arraybuffer//获取全局唯一的文件管理器
let fileManager = wx.getFileSystemManager()
//wx.env.USER_DATA_PATH 获取本地用户文件目录的路径
//filePath  自定义的本地临时路径
let filePath = wx.env.USER_DATA_PATH + '/inner.jpg'
var that = this
//获取到的二维码的图片Buffer写入本地临时路径
fileManager.writeFile({filePath: filePath,//本地临时路径encoding: 'binary',//编码方式,二进制data: data,//这里为后台接口返回的图片Buffersuccess: function (res) {//由于业务需求,需要绘制圆形的二维码,但是后台给的图片是直角的,所以需要自己绘制成圆形utils.drawImage(canvas, ctx, filePath, 531 * rpx, 929 * rpx, 150 * rpx, 150 * rpx, false, true)},fail: function (res) {console.log(res)},
});
其他小技巧
//绘制矩形图案,可支持圆角(借鉴大佬的),代码就不解释了,大家自行研究
//参数r为圆角的像素,color为矩形的填充色
let roundRectColor = function (ctx, x, y, w, h, r, color) { if (w < 2 * r) { r = w / 2; }if (h < 2 * r) { r = h / 2; }ctx.beginPath();ctx.fillStyle = color;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.closePath();
}
module.exports = {roundRectColor
}//引用方法
const utils = require('../../utils/util.js')
const rpx = wx.getSystemInfoSync().windowWidth / 750 //这里是为了内容能在不同设备自适应//绘制X坐标为25px,y坐标为25px,宽度为650px,高度为880px,圆角为10px,填充色为白色的矩形
utils.roundRectColor(ctx, 25 * rpx, 25 * rpx, 650 * rpx, 880 * rpx, 10 * rpx, "#ffffff");//绘制填充色为渐变色的矩形
//创建一个线性的渐变颜色,ctx.createLinearGradient(起点的 x 坐标,起点的 y 坐标,终点的 x 坐标,终点的 y 坐标)
const grd = ctx.createLinearGradient(0, 0, 0, 1100* rpx)
//添加颜色的渐变点,范围 0-1
grd.addColorStop(0, '#FDB862')
grd.addColorStop(1, '#FB4631')
utils.roundRectColor(ctx, 0, 0, 700 * rpx, 1100 * rpx, 20 * rpx, grd);

小程序通过canvas生成海报保存为图片的技巧的技巧我知道的就只有这些了,还有很多技巧我可能还没发现,也请各位大佬指教了。代码不全是我自己写的,有很大一部分也是拿其他大佬的代码改的,我只是个卑微的搬运工罢了

小程序通过canvas生成海报保存为图片的技巧相关推荐

  1. uni-App小程序、canvas 生成海报 +下载图片+分享微信好友

    功能:分享弹窗,生成海报并支持保存,目前仅支持微信小程序 ·插件包链接 https://ext.dcloud.net.cn/plugin?id=586 代码: <template>< ...

  2. 微信小程序利用canvas生成海报-------图片为网络图片

    根据我们老总的业务需求,迫不得已,我做了这个canvas绘制的海报,感觉基本上可以解决现在海报所遇到的大部分问题了,献给那些没有做过的小伙伴们,话不多说,先上我做的效果 上代码 <style&g ...

  3. 小程序画布Canvas生成海报,分享朋友圈

    一 使用场景: 小程序内,想要分享海报到朋友圈,附带小程序码,达到转发引流的目的. /*** 用户点击右上角分享*/onShareAppMessage: function() {},shareBook ...

  4. 微信小程序纯前端生成海报并保存本地

    需求 公司开发微信小程序,有一个海报页面,需要用户点击生成海报,可以将该该swipe-item 生成一个带二维码的图片,最终由纯前端实现! 技术调研 因为小程序的打包限制,不可能将所有的图片都放在代码 ...

  5. 微信小程序使用canvas绘制海报并保存本地相册

    在做微信小程序的时候,很多都会用到生成海报分享功能,刚好最近项目有这个需求,今天就发出来记录下 首先是使用canvas绘制一张海报,微信小程序的canvas有老版本和新版本我是用的是新版本 代码如下 ...

  6. 小程序05 canvas绘图并保存到相册

    小程序现在没有提供直接分享到朋友圈的接口,所以只能采取折中的策略,即先将要分享的内容先保存为图片,保存到用户相册,然后再由用户将该图片分享到朋友圈 生成小程序码需要access token,后端生成比 ...

  7. 微信小程序图片转换成文字_微信小程序中用canvas将文字转成图片,文字自动换行...

    onReady: function () { wx.showLoading({ title: '生成图片中...', }) var that = this const ctx = wx.createC ...

  8. 小程序canvas生成海报保存至手机相册

    小程序canvas画图保存至手机相册 (1)可直接展示生成的海报 .因手机分辨率不同可能导致生成的海报会有细微差别,这里隐藏canvas海报,页面正常设置海报样式保存时保存隐藏的canvas海报 (2 ...

  9. 微信小程序使用canvas生成分享海报功能复盘

    前言 近期需要开发一个微信小程序生成海报分享的功能.在h5一般都会直接采用 html2canvas 或者 dom2image 之类的库直接处理.但是由于小程序不具备传统意义的dom元素,所以也没有办法 ...

最新文章

  1. 单点登录认证方案思路,求好思路回复
  2. centos恢复图形界面_centos图形界面的开启和关闭
  3. 存在即合理的原文 黑格尔
  4. 乐播投延迟很高_大屏也要高刷新!华为4K@120智慧屏初体验,屏幕软件都够硬
  5. Spring Boot:(三)开发Web应用之Thymeleaf篇
  6. 红外遥控c语言,NEC协议红外遥控器
  7. sqlserver 单条update 特别慢_vacuum full执行慢怎么办?
  8. [黑金原创教程][连载][iBoard 电子学堂][第八卷 设计任意波发生器]第三篇 直接数字合成(DDS)原理...
  9. 写的函数符号表里没有_DATEDIF函数,看看你的Excel里有没有?
  10. wox wpm 安装 有道插件
  11. 进行网络数据采集时用 CSS——避免蜜罐
  12. ddr4 dqs 频率_ddr4
  13. Day.js —— 一个轻量型的日期时间库 moment 的完美代替品
  14. 51单片机复习程序例举004--HD44780控制的1602LCD
  15. 【艾琪出品】《数据库课程设计》【参考】
  16. SCNN--车道线检测
  17. chrome 浏览手机网站
  18. 容联携手火星时代教育 促进线上线下一体化
  19. perl 获取匹配正则表达式字串
  20. AVL树 01 AVL树基础

热门文章

  1. 海峡区块链研究院院长王永利:要理性看待区块链
  2. NCS8803 功能:是一颗将HDMI信号转EDP信号的转接芯片。其应用如下:
  3. 推荐一款串口TCP协议调试利器-小平TCP串口调试特工
  4. mysql数据库密码
  5. 主从复制读写分离保姆级教学
  6. 米联客FDMA及其控制器代码逐行讲解,全网最细,不接受反驳
  7. ConstraintLayout跟随吸附效果
  8. 修改el-input样式
  9. Python3.9+原版如何清屏
  10. concat 与 add