小程序只能通过canvas绘制然后生成图片分享出去,不像h5或者网页端的可以通过 html2canvas 相关插件直接将dom转换成图片,所以用canvas实现的效果会比较局限,下面说一下我开发的时候遇到的问题。
开发常见问题
获取canvas元素
因小程序不支持像原生js那样操作dom,可通过如下代码获取
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
})
图形拉伸问题
一般出现这个问题就是开发者通过style直接对canvas的宽高进行设置,会导致原有的画布内容被style设置的宽高进行拉伸,所以设置画布宽高的时候,通过canvas元素设置或者在标签里设置
// 第一种
<canvas type="2d" id="myCanvas" width="xx" height="yy"></canvas>
// 第二种
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
canvas.width = xx
canvas.height = yy
})
图片画布背景等不清晰问题
可以通过获取当前设备的像素比,进行等比例放大,以达到在手机中的高清显示
const canvas = res[0].node
// 获取手机像素比
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = canvasWidth * dpr
canvas.height = canvasHeight * dpr
ctx.scale(dpr, dpr)
小程序图片绘制
小程序不像原生js那样可以new Image(),可以通过如下代码解决
const image = canvas.createImage()
image.src = imageUrl
image.onload = () => {
ctx.drawImage(image, positionX, positionY, imageWidth, imageHeight)
})
绘制线条粗细不准确的问题
当我想绘制0.5px的粗细的线条的时候发现画布展示的有点粗像是1px的粗细,后面在网上搜索到只需要在坐标上加0.5即可
// 绘制线条
drawLine (ctx, color, lineWidth, startX, startY, endX, endY) {
ctx.strokeStyle = color
ctx.lineWidth = lineWidth // 线条粗细
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX, endY)
ctx.closePath()
ctx.stroke()
}
draw(ctx, color, 0.5, 0 + 0.5, 0 + 0.5, 0 + 0.5, 20 + 0.5)
怎么让文本居中
文本居中的话我们需要先获取文本的宽度,进而通过文本宽度计算居中位置
// 获取文本宽度
getTextWidth (ctx, font, text) {
ctx.font = font
const textWidth = ctx.measureText(text).width
return textWidth
}
怎么将画布生成图片并保存
可以先通过canvas.toDataURL('image/png')获取画布生成的图片数据,返回的是base64,所以需要我们将base64转成图片并生成临时存储链接,然后保存到手机里,保存到手机可以通过wx.saveImageToPhotosAlbumAPI来实现,关键是获取图片文件路径
const canvasImgUrl = canvas.toDataURL('image/png')
const fs = wx.getFileSystemManager()
// 临时存储图片路径
const imageUrl = wx.env.USER_DATA_PATH + '/' + new Date().getTime() + '.png'
// 将base64图片写入
fs.writeFile({
filePath: imageUrl,
data: base64.slice(22),
encoding: 'base64',
success: () => {
// ToDo
// imageUrl就是base64转成图片临时链接
}
})
快速开发指北
绘制文本
drawText (ctx, font, color, text, x, y) {
ctx.font = font
ctx.fillStyle = color
ctx.fillText(text, x, y)
}
绘制圆形头像
drawAvatar (canvas, ctx, imageUrl, avatarWidth, avatarHeight, positionX, positionY) {
const avatar = canvas.createImage()
avatar.src = imageUrl
avatar.onload = () => {
ctx.save()
ctx.beginPath()
ctx.arc(avatarWidth / 2 + positionX, avatarHeight / 2 + positionY, avatarWidth / 2, 0, 2 * Math.PI, false)
ctx.clip()
ctx.drawImage(avatar, positionX, positionY, avatarWidth, avatarHeight)
ctx.restore()
}
}
绘制二维码
可以通过qrcode相关插件获取生成的二维码,再添加到画布里,例如QrCode.toDataURL API 可以将二维码转成base64,然后我们就可以通过上面的怎么将画布生成图片并保存指南来实现
绘制圆角矩形
drawRadiusRect (ctx, width, height, positionX, positionY, borderRadius) {
ctx.beginPath()
ctx.moveTo(positionX + borderRadius, positionY)
ctx.lineTo(positionX + width - borderRadius, positionY)
ctx.arcTo(positionX + width, positionY, positionX + width, positionY + borderRadius, borderRadius)
ctx.lineTo(positionX + width, positionY + height - borderRadius)
ctx.arcTo(positionX + width, positionY + height, positionX + width - borderRadius, positionY + height, borderRadius)
ctx.lineTo(positionX + borderRadius, positionY + height)
ctx.arcTo(positionX, positionY + height, positionX, positionY + height - borderRadius, borderRadius)
ctx.lineTo(positionX, positionY + borderRadius)
ctx.arcTo(positionX, positionY, positionX + borderRadius, positionY, borderRadius)
ctx.closePath()
}
canvas绘制毛玻璃
以下结果是通过chatgpt搜出来的,代码可以用,只需要把响应的dom操作换成小程序的方法操作,当需要实现毛玻璃效果的区域太大,在进行高斯模糊算法的时候运算会比较久。
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'myImage.png';
img.onload = function() {
// 将图片绘制到canvas上
ctx.drawImage(img, 0, 0);
// 获取canvas上的图像数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
// 定义模糊半径
const radius = 5;
for (let i = 0; i < pixels.length; i += 4) {
// 获取当前像素的位置
const x = (i / 4) % canvas.width;
const y = Math.floor(i / 4 / canvas.width);
let r = 0,
g = 0,
b = 0,
count = 0;
// 对当前像素及其周围像素进行高斯模糊处理
for (let j = -radius; j <= radius; j++) {
for (let k = -radius; k <= radius; k++) {
const pixelIndex = ((y + k) * canvas.width + (x + j)) * 4;
if (pixels[pixelIndex] !== undefined) {
r += pixels[pixelIndex];
g += pixels[pixelIndex + 1];
b += pixels[pixelIndex + 2];
count++;
}
}
}
// 计算平均颜色值
const avgR = Math.floor(r / count);
const avgG = Math.floor(g / count);
const avgB = Math.floor(b / count);
// 将当前像素的颜色值设置为平均颜色值
pixels[i] = avgR;
pixels[i + 1] = avgG;
pixels[i + 2] = avgB;
}
// 将处理后的图像数据绘制到canvas上
ctx.putImageData(imageData, 0, 0);
};
以上是我在开发分享图片遇到的问题,欢迎各位大佬提出问题一起讨论