小程序通过canvas生成海报保存为图片的技巧
最近公司要求在小程序点击分享,要生成一张图片,可以保存在用户相册里,图片里的内容根据后台返回的数据生成,这就涉及到小程序画布的知识了,因为微信文档上,画布的Api很多已经废弃了,而且新的Api怎么用也没写的很清楚,所以就踩了很多坑。经过我查阅了大量大佬写过的文章,自己也总结一些方法,避免后面的人踩坑。
声明canvas的组件
<canvas disable-scroll="true" type="2d" id="myCanvas" canvas-id="card" style="width: 700rpx;height: 1100rpx;"></canvas>
(这里注意,属性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>
data: {
canvasLeft: "200%"
},
#myCanvas {
border-radius: 20rpx;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
下图所示是新 Canvas 2D 实例的代码,还在用wx.createCanvasContext的小伙伴请停止使用,因为官方已经废除了。(注意,使用微信开发工具的真机调试画布会报错,这是微信开发工具的bug,如果要看画布在真机上的效果,请用预览的功能)
draw() {
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
this.data.canvasNode = 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)
})
},
绘制画布内容
绘制文本
const rpx = wx.getSystemInfoSync().windowWidth / 750
ctx.font = `${24 * rpx}px sans-serif`;
ctx.fillStyle = '#999999'
ctx.fillText('佣金比例:', 269 * rpx, 252 * rpx);
const rpx = wx.getSystemInfoSync().windowWidth / 750
ctx.font = `bold ${34 * rpx}px sans-serif`;
ctx.fillStyle = 'red'
ctx.fillText('佣金比例:', 379 * rpx, 210 * rpx);
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);
} 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,
y: 39 * rpx,
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 = imgsrc
img.onload = function () {
ctx.save();
if (isAlpha) {
ctx.globalAlpha = globalAlpha
} else {
ctx.globalAlpha = "1"
}
if (isRange) {
ctx.beginPath();
ctx.arc(width / 2 + x, height / 2 + y, width / 2, 0, Math.PI * 2, false);
ctx.clip();
}
ctx.drawImage(img, x, y, width, height);
ctx.restore();
resolve()
}
img.onerror = (err) => {
console.log(err)
reject(err)
}
})
}
module.exports = {
drawImage
}
const utils = require('../../utils/util.js')
const rpx = wx.getSystemInfoSync().windowWidth / 750
utils.drawImage(canvas, ctx, '../../utils/image/card/logo.png', 489 * rpx, 71 * rpx, 172 * rpx, 74 * rpx, true,0.2,false)
wx.getImageInfo({
src: '此处为网路图片的路径',
success: function (res) {
wx.compressImage({
src: res.path,
quality: 1,
complete: function (res) {
utils.drawImage(canvas, ctx, res.tempFilePath, 49 * rpx, 55 * rpx, 200 * rpx, 200 * rpx)
}
})
}
})
let fileManager = wx.getFileSystemManager()
let filePath = wx.env.USER_DATA_PATH + '/inner.jpg'
var that = this
fileManager.writeFile({
filePath: filePath,
encoding: 'binary',
data: data,
success: function (res) {
utils.drawImage(canvas, ctx, filePath, 531 * rpx, 929 * rpx, 150 * rpx, 150 * rpx, false, true)
},
fail: function (res) {
console.log(res)
},
});
其他小技巧
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
utils.roundRectColor(ctx, 25 * rpx, 25 * rpx, 650 * rpx, 880 * rpx, 10 * rpx, "#ffffff");
const grd = ctx.createLinearGradient(0, 0, 0, 1100* rpx)
grd.addColorStop(0, '#FDB862')
grd.addColorStop(1, '#FB4631')
utils.roundRectColor(ctx, 0, 0, 700 * rpx, 1100 * rpx, 20 * rpx, grd);
小程序通过canvas生成海报保存为图片的技巧的技巧我知道的就只有这些了,还有很多技巧我可能还没发现,也请各位大佬指教了。代码不全是我自己写的,有很大一部分也是拿其他大佬的代码改的,我只是个卑微的搬运工罢了
|