小程序生成分享图片指北

707 阅读4分钟

小程序只能通过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);
};

以上是我在开发分享图片遇到的问题,欢迎各位大佬提出问题一起讨论