评论

用小程序Canvas 2D自定义生成转发图片

一种使用小程序Canvas 2D自定义生成转发图片

前言

小程序原先转发给别人,要不是一张图片,要不是就是截屏,不是很自定义化。

我们要每个用户在不同的页面,转发的内容都不一样,这当然可以直接从服务端实时生成这样 5:4 的图片做转发,毕竟好处是避免了兼容性问题

本示例将会用另外一种思路,从客户端实时生成分享图片,并进行转发。

技术调查

微信小程序中 以 wx.createCanvasContext 为代表的 CanvasContext (v1)

都从基础库 2.9.0 开始,停止维护了

所以我们就使用更加贴近 mdn 上 Canvas 来代替 (v2)

这里简称停止维护的版本为 v1, Canvas 版本为 v2

值得注意的是

v2 版本中还有一个 wx.createOffscreenCanvas

它也可以使用 OffscreenCanvas.getContext('2d') 这样的 api

不过它的最低版本为 2.16.1

到今天 (2021/05/05) 大部分人用的版本还是 2.16.1 (2.17.0 还在灰度)

所以说,从兼容性的角度出发, 使用 OffscreenCanvas 是有很大风险的

而且我出于好奇实验使用了一下,bug 也是一大堆 issue

开始吧

ps: canvas api 不熟的 mdn 上看看

绘图前言

  • rpx 这种单位换算问题 , wx.getSystemInfoSync 获取一下屏幕宽,做个换算就行
  • 小程序中的 canvas 2d 不能把它设置 style:display:none ,不然转化成图片时会空白 , 我们可以把它移出可视界面。
  • 层级问题,canvas 和 svg 一样都是后面的覆盖前面的,所以我们可以使用 对zIndex 进行排序,来决定每个 function 的执行顺序

绘图 ing

  • 圆角矩形可以使用 arc api 来解决

  • 圆角图片可以使用 clip + drawImage

比如在这里因为我们需要使用微信的头像

所以需要把 图片的域名 在后台配置一下

这里贴一段 uni 下,在 ctx 中绘制圆角头像的代码

async addAvatar() {
      // 下载到本地
      const [err, res] = await uni.getImageInfo({
        src: avatarUrl
      })
      const { path, type } = res
      const img = canvas.createImage()
      img.src = path
      // 直接设置 width height 是不生效
      const offsetX = 20 * dpr
      const offsetY = 20 * dpr
      const r = (50 * dpr) / 2
      const circle = {
            x: offsetX + r,
            y: offsetY + r,
            r: r
          }
      // 这样写是因为最后绘制的时候是执行一个 ()=> Promise<any>
      // 这样可以确保生成的时候,图片已经 onload了
      await new Promise((resolve, reject) => {
        img.onload = () => {
          ctx.save()
          // 切圆角
          ctx.beginPath()
          ctx.arc(circle.x, circle.y, circle.r, Math.PI * 2, false)
          ctx.clip()
          // 画成指定的长和高
          ctx.drawImage(img, offsetX, offsetY, 50 * dpr, 50 * dpr)
          ctx.restore()
          resolve()
        }
        img.onerror = event => {
          reject(event)
        }
      })
    }

图片的丰富度 -> svg 的引入

在上面,我们已经把图片的,渐变,排版,文字,依赖的图片全部绘制上去了

然而这时候我们想加一些丰富度,但是不想使用本地图片去那种,缝合的事情,毕竟这样也有失水准。

这时候我们自然而然的想到了 SVG-to-canvas parser

目前在 npm 主要有 fabric / canvg 这些

这里我选用的是 基于 canvgsvg2canvas

这样我们在 iconfont 就可以挑选一些 svg 下载下来,然后通过 converter 就可以直接转换成 canvas js code 使用了。

当然,转化实际上是不完美的,定位和大小,我们可以改造绘制的 draw function

在其中加入

ctx.translate(x, y)
ctx.scale(dpr / 2, dpr / 2)

颜色什么当然都可以作为参数

这样,我们的 svg 就顺利的加了上去

onShareAppMessage 转发

onShareAppMessage 是支持 promise 的,不过 promise 三秒内不 resolve , 会自动 fallback

onShareAppMessage 文档

这时候,我们就可以写出下列代码

onShareAppMessage() {
  const createPromise = async () => {
      try {
        this.shareBtnLoading = true
        // 获得图片的临时路径
        const loaclPath = await aWayToGetCanvasTempFilePath()

        return {
          title: 'hello, i am icebreaker',
          path: 'resolve path',
          imageUrl: loaclPath
        }
      } catch (e) {
        console.warn(e)
      } finally {
        this.shareBtnLoading = false
      }
  }
  return {
    title: 'fallback title',
    promise: createPromise(),
    path: 'fallback path',
    imageUrl: 'fallback imageUrl'
  }
}

获得图片的临时路径,可以使用 wx.canvasToTempFilePath api

这个api是 画布 v1 版本 和 v2版本通用的

不过 v1 版本传入的是 canvas-id

v2 则是 canvas 组件实例

生成速度

在不同的 dpr 下,时间都很快 真机在 300ms 左右 远小于 3000ms 自动 fallback 的限制

效果视频

在知乎上, 链接地址

最后一次编辑于  2021-05-05  
点赞 1
收藏
评论

3 个评论

  • Stobbon
    Stobbon
    2022-09-09

    楼主可以分享源码嘛


    2022-09-09
    赞同
    回复
  • 雨停了
    雨停了
    2021-11-08

    楼主 使用 wx.canvasToTempFilePath api 时 没报错吗

    2021-11-08
    赞同
    回复 1
    • ice breaker
      ice breaker
      2021-11-12
      没有报错啊
      2021-11-12
      回复
  • 栉风
    栉风
    2021-09-28

    源码呢

    2021-09-28
    赞同
    回复 2
    • ice breaker
      ice breaker
      2021-09-28
      在 “程序员名片” 这个小程序上有展示效果,由于这个在一个私有的仓库里面,源码不便公开。有需求可以问我要
      2021-09-28
      回复
    • A灰灰辉辉
      A灰灰辉辉
      04-07回复ice breaker
      大佬,那个源码能发一下么?
      04-07
      回复
登录 后发表内容