「强烈建议收藏」小程序canvas绘制带二维码海报全流程(枚举踩坑,详解解决方案)

2021-06-01 10:34:53 浏览数 (1)

接下来,我会把纯前端实现生成带二维码的海报全流程给大家讲个明明白白,把我自己遇到的坑,给大家详细分享并讲解,防止大家遇到相似问题,即使遇到问题,也会有一个明确的方向,并且吐血建议大家收藏一波,以备不时之需。(你不能保证以后的需求,没有类似的吧,有的话,记得翻出来看看)

长路漫漫,总得有人敢于迈出踩坑的第一步,我想我就是那个??,希望大家能踏着我冒着抬上救护车危险填平的坑,通向小程序生成海报胜利之路。送人玫瑰,手留余香,阅读的朋友可以给笔者 点赞,关注一波 陆续更新超干,超硬核的前端文章。

一 写在前面

1 canvas绘制带二维码的海报,这些坑总有一个你可能会踩到,我会带你一步步解决这些坑

技术选型背景:taro3.0-vue

先来十一个问题压压惊,相信你做绘制海报过程中,一定会遇到

taro框架遇到的坑

① taro-vue createCanvasContext 获取canvas实例无效问题,绘制不出来效果?✅

② taro-vue 初始化获取不到canvas上下文怎么办,完全绘制不出来图片?✅

小程序canvas遇到的坑

③ 关于canvas 宽高以及缩放比问题,绘制的元素变形,画布的高度真得等于cavans标签设置的宽高么?✅

④ canvas怎么绘制叠在一起的两张图片,并控制层级?✅

⑤ 如何用canvas绘制,多行文本?✅

⑥ 如何根据设计稿,精确还原海报各个元素位置问题。✅

⑦ canvas怎么绘制base64的图片✅

⑧ 如何绘制网络的图片,两种canvas画布api,绘制图片有什么区别完成✅

生成二维码遇到的坑

⑨ 如何正确选型生成二维码工具?✅

⑩ 生成的二维码,识别不出来怎么办,✅

⑪ 如何绘制二维码上的logo✅

2 实现效果

二 实战一第一阶段:小程序canva初始化

1 两种cavnas获取上下文方式

我们即将解决的问题

① taro-vue createCanvasContext 获取canvas实例无效问题,绘制不出来效果?

② taro-vue 初始化获取不到canvas上下文怎么办?

微信官网上介绍两种 canvas 获取上下文方式,一种是老的api ,一种是新的 api ,接下来我将讲解一下这两api的用法。

老版本 createCanvasContext 方式

createCanvasContext是微信提供的获取 canvas实例的老得接口,使用方式如下。

wxml

代码语言:javascript复制
<canvas style="width: 300px; height: 200px;" canvas-id="firstCanvas"></canvas>

美好的一天从写一个hello,world开始。

js中这么写

代码语言:javascript复制
onReady(){
    /*  使用 wx.createContext 获取绘图上下文 context , firstCanvas 与 canvas 属性中的canvas-id一一对应  */
    const context = wx.createCanvasContext('firstCanvas')
    /* 设置字体大小 */
    context.setFontSize(20) 
    /* 设置字体颜色 */
    context.setFillStyle('pink')
    /* 设置文本内容,位置 */
    context.fillText('hello,world', 0, 0)
    context.draw()
}

老版本是使用createCanvasContext传入 canvas标签中的 canvas-id属性,来获取canvas实例,老版本的使用起来说实话,不够灵活,很多对canvas线条,颜色的设置,都封装成方法了,每次改变需要调用方法。

新版本 getContext 上下文方式

新的方式,则是先通过 createSelectorQuery 获取 canvas 元素节点, 然后通过 getContext 获取上下文。

wxml

代码语言:javascript复制
 <canvas type="2d" id="myCanvas"></canvas>

js

代码语言:javascript复制
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({
    node: true,
    size: true
})
.exec((res)=>{
    const { node } = res[0]
    if (!node) return
    /* 获取 canvas 实例 */
    const context = node.getContext('2d')
    context.fillStyle = 'pink'
    /* 设置字体样式 大小 字体类别 */
    context.font = 'normal 400 12px PingFangSC-Regular',
    context.fillText('hello,world', 0, 0)
})

这种方式和第一种 createSelectorQuery 方式,在api使用方式上会有微妙的差别,这种写法更像原生的DOM写法,设置颜色,样式,直接改变context属性,而不再需要调用对应的api

taro-vue 使用 canvas

解决问题:① taro-vue createCanvasContext 获取canvas实例无效问题,绘制不出来效果?

因为我们小程序技术选择是 taro-vue2,所以我这里重点讲一下,在taro-vue中,目前使用 createCanvasContext 方式获取 canvas 实例,绘制画布从来没有成功过,即便是createCanvasContext能够创建上下文,但是任何东西也画不出来(传this之类的方案试了一个遍)。要是问我为什么?实际我也不知道,只有凹凸实验室的同学应该更清楚,GitHub上也有issue,希望taro团队能够重视起来。

解决方案就是采用最新的api,就是上述讲的第二个方案。代码如下:

代码语言:javascript复制
import Taro  from '@tarojs/taro'
const query = Taro.createSelectorQuery()
query.select('#myCanvas')
.fields({
    node: true,
    size: true
})
.exec(res=>{
    //TODO:....
})

② taro-vue 初始化获取不到canvas上下文怎么办?

在使用taro-vue的过程中,会面临一个问题,就是小程序node节点获取不到的问题,这个有可能是小程序本身的生命周期,和vue生命周期混乱造成的。尤其当我们选择的是组件而不是页面的情况。对于这样的情况,官方文档给出了答案。页面首次渲染完毕时执行,此生命周期在小程序端对应小程序页面的 onReady 生命周期。从此生命周期开始可以使用 createCanvasContext 或 createselectorquery 等 API 访问真实 DOM。

也就是说如果想要获取真是dom节点,我们可以这么做,

组件中

代码语言:javascript复制
mounted () {
    eventCenter.once(getCurrentInstance().router.onReady, () => {
       const query = Taro.createSelectorQuery()
        query.select('#myCanvas')
        .fields({
           node: true,
           size: true
        })
        .exec(res=>{
        //TODO:....
        })         
    })
}

尴尬的是,这种情况下,有的时候会造成 eventCenter.once() 回调函数不执行的情况,比如说当前组件的是受到v-if控制的情况。那么怎么样解决呢,对于这种情况,我教大家一种解决方案。

我们可以用taro中,通过 Taro.nextTick 方法,将获取元素的任务放在下一次nextTick执行。

代码语言:javascript复制
mounted(){
  Taro.nextTick(() => {
      // 获取元素
  })   
}

2 初始化 canvas设置宽高百分比

我们即将解决的问题:

③ 关于canvas 宽高以及缩放比问题,绘制的元素变形,画布的高度真得等于cavans标签设置的宽高么?

代码语言:javascript复制
<template>
    <view>
        <canvas
            id="myPoster"
            type="2d"
            class="canves"
            :style="canvasStyle"
        />
    </view>
</<template>

在这里我们首先要明白二个概念, 容器宽高: 我们给canvas标签设置的宽高,就是如上代码中的 canvasStyle,是canvas容器的宽高。 画布宽高: 而我们画布的宽高,在新版本api中,是通过获取node节点,动态设置的node.widthnode.height的值。

我们期望将整个屏幕作为画布,对于不同手机,屏幕尺寸都会有差别,所以要动态获取设备的宽高。这里有一个问题是 容器宽高等于画布宽高吗 , 答案是否定的,为什么这么说呢,原因如下 小程序的canvas画布有一个原始的画布宽高,以及缩放比,而且是按照一倍像素来的,当我们给canvas容器设定容器宽高之后,如果没有对应设置canvas画布的画布宽高以及scale,画出的画布就会严重的变形,我们用一个例子来解释。

比如我们想再画布上半部分区域,画一个图片,当我们期望正常比例画 canvas ,如果我们只给cavans标签加宽高,而不给画布设置宽高的时候。会按照原始画布的宽高比去绘制。

期望结果,画布充满屏幕,图片按照正常比列展示。当我们不给 cavnas 画布设置画布宽高 以及缩放比的时候。会发生下面的情况。

实际效果:

所以我们初始化的时候要给canvas如下操作。这个在微信的官方文档中,都有说明。

代码语言:javascript复制
import Taro, {
    eventCenter,
    getCurrentInstance
} from '@tarojs/taro'

export default {
    
    name:'myPoster',
    data(){
        const {
            windowHeight,
            windowWidth,
            pixelRatio
        } = Taro.getSystemInfoSync() /* 动态获取设备的宽和高  */
       return {
            canvasStyle: {           /* cavnas 的宽高 */
                width: windowWidth   'px',
                height: windowHeight   'px',
            },
            windowWidth,
            pixelRatio,   /* 屏幕缩放比 */
            windowHeight,
            scale:1       
       }
    },
    mounted(){
        Taro.nextTick(() => {
            const query = Taro.createSelectorQuery()
            query.select('#myPoster').fields({
                node: true,
                size: true
            }).exec(res => {
                let {
                    node,
                } = res[0]
                if (!node) return
                 /* 第一步: canvas 画布的宽高 和 元素的宽高 必须保持相同的长宽比列,否则会变形 */
                const dpr = this.pixelRatio
                const context = node.getContext('2d')
                node.width = windowWidth * dpr
                node.height = windowHeight * dpr
                context.scale(dpr, dpr)
                context.fillStyle = '#fff'
                context.fillRect(0, 0, windowWidth, windowHeight)
            })
        })
    }
}

当我们设置好画布宽高,以及缩放比之后,就能按照正常比列进行绘制了。让我们一起看看设置完缩放比之后的图片效果,变成了我们想要的效果。

接下来就是绘制阶段。

三 实战第二阶段:虚拟点位绘制canvas阶段

在讲解canvas如何生成海报,完美还原设计稿的问题之前,我们应该想一个问题,因为canvas画布,毕竟不是 dom模型,可以使用div或者view,通过自定义设置样式来进行布局。cavnas需要我们画出元素的布局效果,这里就要精确获取画布上每一个元素相对与画布的x,y值。那么首先想到的是如何获取每一个元素精确的x , y 值。

1 虚拟点位还原实际设计稿

解决问题: ⑥ 如何根据设计稿,精确还原海报各个元素位置问题。 针对完美还原设计稿的问题,比较靠谱的方案就是,先1:1正常挂在dom元素,然后通过获取元素的位置,来绘制canvas画布的元素位置。我们用一幅图来表示其原理。

注意事项
注意事项1: 选择正确的元素获取点

这里打一个比方,我们在dom元素中可能存在这样的结构。

代码语言:javascript复制
<view class="box" >
    <view class="parent" >
        <view class="son" > 这里是将要绘制到canvas中的内容。</view>
    </view>
</view>

对于上面的结构,我们只需要将 son中的内容绘制到 canvas 画布中,那么就有一个问题,我们要获取哪一层级的元素信息(left,top,width,height),答案应该都能猜到,应该是想要绘制的内容最近的一层,也就是面的son层级。如果我们选外层,可能收到父元素paddingmargin等影响,导致真实的位置不准确。

注意事项2: 尽量不要给获取信息的元素增加 padding marign,如果绘制文本内容,尽量容器高度等于文本高度

还有一个问题,就是尽量不要给需要绘制的元素,增加 padding marign等属性,如果是绘制纯文本,不要设置lineHeight,如图下示例:

我们期望在获取 a 点的位置信息, 但是最终却获取 b点的位置信息。如果用 b 点位置来绘制canvas,势必不能完美还原设计稿,所以我们在用这种方式绘制canvas的时候,应该注意这些细节问题。

封装获取位置信息方法

我们需要绘制海报上的每一个点位,首先想到的就是获取小程序元素位置方法,并封装该方法。我们用promise来防止深层次的回调,并且方便使用async await语法糖。废话不多说,一言不合上代码。

代码语言:javascript复制
    /* 获取元素位置 */
    geDomPostion(dom, isAll) {
        return new Promise((resolve) => {
            Taro.createSelectorQuery().select(dom).boundingClientRect(rect => {
                const {
                    top,
                    left
                } = rect
                /* isAll 是否获取设备宽高等信息 */
                resolve(isAll ? rect : {
                    top,
                    left
                })
            }).exec()
        })
    },

小提示:如果用wx原生,或者其他跨端框架mpvue wepy uniapp是的同学,把 Taro 换成 wx 即可。

2 绘制网络图片

绘制网络图片

接下来我们要解决的问题: ⑨ 如何绘制网络的图片,两种通过canvas画布api,绘制图片有什么区别?

我们在用canvas绘制图片的时候,对于本地图片可以直接通过canvas提供的drawImage进行绘制,但是对于网络图片是不能这么绘制的,我们首先需要通过getImageInfo来获取图片的临时路径。用getImageInfo绘制网络资源的时候请注意配置一下合法的下载域名,要不然我们是无法成功获取图片信息的。我们首先需要在小程序后台配置downloadFile合法域名。

具体步骤如下:第一步:

第二步:

第三步:

接下来我们要做的就是读取图片的临时路径,绘制到canvas画布上来。

代码语言:javascript复制
 /* backGroundImageUrl 是我们要画的网络图片的地址  */
 this.getImageInfo(this.backGroundImageUrl).then(res=>{
      const {
        width,   /* 宽度 */
        height,  
        path     /* 临时路径 */
      } = res1
      /* 第二步: 绘制banner图 */
    const bannerImage = await this.geDomPostion('#bannerImage')
    this.startTop = bannerImage.top - 30
    this.drawImage(context, node, path, 0, 0, width, height, 0, this.startTop, windowWidth, windowWidth)
    context.save()
 })

this.drawImage 是我们封装好的方法,之前说过对于小程序获取 context两种接口方式,两种方式绘制canvas图片,有一些差别,我们马上道来。

新老接口绘制图片的区别

老版本绘制方法

老版本api createCanvasContext可以直接使用 drawImage绘制图片。如下

代码语言:javascript复制
/* 绘制图片 */
context.drawImage(url,x,y,width,height,dx,dy,dwidth,dheight)

当时我们项目用的是第二种新api getContext当时获取上下文,所以在图片绘制方式上,会有所改变。

新版本绘制方法

代码语言:javascript复制
  const image = node.createImage()
  image.src = url
  image.onload = () => {
    context.drawImage(image,x,y,width,height,dx,dy,dwidth,dheight)
  }

用新版本的API 绘制图片的同学请注意,这个onload回调是在图片加载完成时候执行的,所以说明是异步的。还有一个注意的地方,相比老版本的 drawImage 第一个参数是图片的路径,而新版本的drawImage第一个参数是image元素。

封装绘制图片方法

刚才在绘制网络图片最后一步,我们调用了 this.drawImage 方法。因为整个海报生成过程中,内部会画入多张图片,所以我们单独封装了一个绘制图片的方法。

代码语言:javascript复制
/* 绘制图片 */
drawImage(context, node, url, ...arg) {
    return new Promise((resolve) => {
        const image = node.createImage()
        image.src = url
        image.onload = () => {
            context.drawImage(image, ...arg)
            resolve()
        }
    })
},

这样我们就可以通过,async,await判断图片是否加载完成。

简介 context.drawImage

我这里简单给大家介绍一下context.drawImage用法,

代码语言:javascript复制
CanvasContext.drawImage(imageResource / dom, sx,  sy,  sWidth,  sHeight,  dx,  dy,  dWidth,  dHeight)

绘制图像到画布,第一个参数,在老api中代表路径,在新版本api中代表imagDom元素,

sx 需要绘制到画布中的,imageResource / dom 的矩形(裁剪)选择框的左上角 x 坐标

sy 需要绘制到画布中的,imageResource / dom 的矩形(裁剪)选择框的左上角 y 坐标

sWidth 需要绘制到画布中的,imageResource / dom 的矩形(裁剪)选择框的宽度

sHeight 需要绘制到画布中的,imageResource / dom 的矩形(裁剪)选择框的高度

dx imageResource的左上角在目标 canvas 上 x 轴的位置

dy imageResource的左上角在目标 canvas 上 y 轴的位置

dWidth 在目标画布上绘制imageResource的宽度,允许对绘制的imageResource进行缩放

dHeight 在目标画布上绘制imageResource的高度,允许对绘制的imageResource进行缩放

我们用一幅图表示各个属性的对应什么。

3 绘制层级图片

解决问题: ④ canvas怎么绘制叠在一起的两张图片,并控制层级?

如果我们绘制叠在一起的两张图片,需要我们做一些什么样的工作呢?首先想到的是层级问题,我们期望背景图片放在下面,例如头像之类的图片放在上面,但是在画布中没有控制zIndex层级的属性,那么怎么样处理这个问题呢 ?答案是实际在canvas中,绘制的先后顺序 就是画布层级顺序,后画的在先画的上层,那么对于这种层级问题呢,我们只要保证层级高的元素后画,层级低的元素先画就可以完美解决,接下来我们在海报中,画上头像,文字等信息。

代码语言:javascript复制
<!-- 头像区 -->
<image  class="userheadImage"  id="userheadImage"  :src="headImage"  />

代码语言:javascript复制
    /*TODO: 绘制头像 */
    const userheadImage = await this.geDomPostion('#userheadImage',true)
    /* 圆形图片 */
    let d = userheadImage.height / 2
    const cx = userheadImage.left   userheadImage.width / 2
    let cy = userheadImage.top   userheadImage.height / 2
    context.arc(cx, cy, d, 0, 2 * Math.PI)
    context.strokeStyle = '#FFFFFF'
    context.stroke()
    context.clip()
    await this.drawImage(context, node, this.headImage, userheadImage.left, userheadImage.top, userheadImage.width, userheadImage.height)
    context.restore()
    this.drawText(context,{ top: userheadImage.top   userheadImage.height   40 ,left : userheadImage.left - 70 },'我不是外星人「前端Sharing」',18,'normal 600 20px PingFangSC-Regular','#fff')

在我们使用context.clip()之后,记得使用context.restore()重置,否则将无法绘制其他元素。

效果:

我们完美解决了片文本的层级问题,接下来,我们就要绘制海报的主要的内容了。在我们绘制海报的时候,可能会遇到多行文本的情况,那么多对多行文本,我们是怎么解决的呢?

4 绘制多行文本

解决问题:⑤ 如何用canvas绘制,多行文本?

canvas画的文本,并不能像我们的dom元素下的文本一样,可以自动换行,我们如何还原,多行文本的效果呢。这这里教大家一种方法,我们可以一个一个字的绘制到canvas中,然后把每个字的宽度相加,如果总宽度大于容器的宽度,那么就另外起一行,增加每一行的高度,从头开始画。,我们直接上代码。

代码语言:javascript复制
       /** 画多行文本
         * @param ctx          canvas 上下文
         * @param str          多行文本
         * @param initHeight   容器初始 top值
         * @param initWidth    容器初始 left值
         * @param canvasWidth  容器宽度
         */
        drawRanksTexts(ctx, str, initHeight, initWidth, canvasWidth) {
            let lineWidth = 0;
            let lastSubStrIndex = 0;
            /* 设置文字样式 */
            ctx.fillStyle = "#303133"
            ctx.font = 'normal 400 15px  PingFangSC-Regular'
            for (let i = 0; i < str.length; i  ) {
                lineWidth  = ctx.measureText(str[i]).width
                if (lineWidth > canvasWidth) { /* 换行 */
                    ctx.fillText(str.substring(lastSubStrIndex, i), initWidth, initHeight)
                    initHeight  = 20
                    lineWidth = 0
                    lastSubStrIndex = i
                }
                if (i == str.length - 1) {  /* 无需换行 */
                    ctx.fillText(str.substring(lastSubStrIndex, i   1), initWidth, initHeight)
                }
            }

        },

调用

代码语言:javascript复制
/* TODO: 复制多行文本 */
const rowsText = await this.geDomPostion('#context', true)
this.drawRanksTexts(context, this.skuName, rowsText.top, rowsText.left, rowsText.width)

效果

四 实战第三阶段:生成二维码

接下来我们做的是绘制二维码,绘制二维码过程,笔者踩了不少的坑,尤其taro-vue不支持createCanvasContext方式,希望我能用自己踩的坑,让大家避开相同的错误,避免大家少走很多弯路。绘制二维码实际并没有想象的复杂,实际就是将链接转换成二维码,让手机扫码或者长按可以识别即可,虽然原理很简单,但是还是有很多注意的细节。

绘制二维码无异于二种方式,第一种方式就是用canvas画出来。第二种将链接转成base64的链接,然后让图片展示链接。 接下来我们针对这两种方式,进行二维码库的技术选型。

1 关于二维码库选型

解决问题 ⑨ 如何正确选型生成二维码工具?

形成二维码的过程,我们肯定不能手撸算法,因为即便我们能手撸出来,也会占用大量时间,还会有很多bug,因为现在生成二维码的生态已经很健全了,比如 qrcode.js等等都是非常不错的,但是唯一不好的是不支持小程序端。我这里介绍几个二维码的库

weapp-qrcode

对于比如短链接比如 笔者的 GitHub https://github.com/GoodLuckAlien 或者 掘金首页 https://juejin.cn/user/2418581313687390 这种 ,都属于短链接,不必拼写很长的参数,这种情况用 weapp-qrcode 绰绰有余。这种方式是基于第一种用canvas绘制的。而且是采用老版本的api , 这样的话就有一个问题,如果像用新的 getContext 方式,就需要把源码下载下来,然后改动一下源码,让它支持 getContext 这种方式。我们来简介一下 weapp-qrcode的使用。

使用

代码语言:javascript复制
//  将 dist 目录下,weapp.qrcode.esm.js 复制到项目目录中  如果用 taro uniapp 等框架 ,可以用  npm install 
import drawQrcode from '../../utils/weapp.qrcode.esm.js'

drawQrcode({
  width: 200,
  height: 200,
  canvasId: 'myQrcode',
  // ctx: wx.createCanvasContext('myQrcode'),
  text: 'https://juejin.cn/user/2418581313687390',
  // v1.0.0 版本支持在二维码上绘制图片
  image: {
    imageResource: '../../images/icon.png',
    dx: 70,
    dy: 70,
    dWidth: 60,
    dHeight: 60
  }
})

结果

这种方式下,最后确实成功了,因为在做demo的时候,我用的是github短链接。但是一回归笔者公司的项目,很长的链接,奈何生成的二维码特别密集,手机根本识别不出来,无奈前功尽弃了,只能换其他的技术方案,所以笔者选择了第二种比较稳的方式,形成base64文件。

qrcode-base64

qrcode-base64 是将二维码的链接,转成base64的链接,并把这个链接作为src属性赋值给图片。我们先介绍一下基本用法。

下载

代码语言:javascript复制
npm install qrcode-base64

使用

代码语言:javascript复制
import QR from 'qrcode-base64'

var imgData = QR.drawImg(this.data.codeText, {
    typeNumber: 4,
    errorCorrectLevel: 'M',
    size: 500
})
// 返回输出base64编码imgData

如上述代码块所示,imgData就是生成的base64链接,我们可以直接把它作为图片的src,然后让canvas将图片绘制到我们的海报中去,但是又来了一个问题,canvas是不支持绘制base64的链接图片的,真机上没有任何效果,真实一步十个坑啊,我们还得想办法解决这个问题。

2 canvas 绘制 base64图片

解决问题 ⑦ canvas怎么绘制base64的图片

对于上面说到的canvas不支持base64的图片,那么我们还要把二维码绘制到海报中,那么并不是没有办法,我们可以用小程序提供的文件系统来解决问题。

小程序文件系统

wx.getFileSystemManager 获取全局唯一的文件管理器,返回值 类似于node中的fs.

writeFile 写入文件,可以将图片写入系统中。

代码语言:javascript复制
const fs = wx.getFileSystemManager()

fs.writeFile(/* 写入文件 */)
封装方法

封装绘制二维码方法

代码语言:javascript复制
  /* 生成二维码 */
        drawCode(ctx, node, x, y) {
            return new Promise((resolve) => {
                const codeImageWidth = 150   /* 绘制二维码宽度 */
                const canvasImageWidth = 85 /* 二维码绘制到canvas的宽度 */
                const left = x - 15          /* left 值 */
                const top = y - 22           /* top 值 */
                const LogoWidth = 15       /* 二维码logo宽度 */
                const url = 'https://juejin.cn/user/2418581313687390'
                
                const base64 = QR.drawImg(url, {
                    typeNumber: 4,
                    errorCorrectLevel: 'L',
                    size: codeImageWidth
                })
                /* 创建读写流 */
                const fs = Taro.getFileSystemManager()
                const times = new Date().getTime()
                const codeimg = Taro.env.USER_DATA_PATH   '/'   times   '.png'

                /* 将base64图片写入 */
                fs.writeFile({
                    filePath: codeimg,
                    data: base64.slice(22),  /* 数据流 */
                    encoding: 'base64',      
                    success: async () => {
                        const offset = (canvasImageWidth - LogoWidth) / 2 /* 偏移量 */
                         /* 绘制图片 */
                        await this.drawImage(ctx, node, codeimg, 0, 0, codeImageWidth, codeImageWidth, left, top, canvasImageWidth, canvasImageWidth)
                        await this.drawImage(ctx, node, this.logoUrl, left   offset, top   offset, LogoWidth, LogoWidth)
                        resolve()

                    }
                })
            })

        },

如上所示我们完成了二维码的绘制。让我们来看一下如何使用。

使用

我们在wxml上写一个元素,作为占位,方便我们可以获取二维码的位置。

代码语言:javascript复制
<view id="qrCode" class="store-uscode" />
代码语言:javascript复制
/*TODO: 第四步:绘制二维码 */
const qrCode = await this.geDomPostion('#qrCode')
await this.drawCode(context, node, qrCode.left - 20, qrCode.top - this.cavnsOffsetop)

效果

扫描成功

3 调试二维码大小,如何让二维码可以识别,绘制二维码logo

解决问题:⑩ 生成的二维码,识别不出来怎么办。

有的时候我们展示的二维码比较小的时候,因为色块太密,手机也会有无法识别的情况。那么我们如何调整二维码,有能让页面尽量高保真的还原设计稿呢,这里教大家一个小技巧,可以去先去二维码生成网站,先适配手机可以识别的最佳比例,避免识别不出来的情况。推荐网站:草料二维码 : https://cli.im/ 我们可以在线调试二维码的像素,和 logo的大小,直到调整出,能够符合设计的最佳大小。

在线调整二维码

微调整 有的时候,我们需要对二维码大小进行微调整,我这里建议在调试阶段,建立起常量控制,并调整写好调整方法或公式。这样做的好处是,每当我们作出微调整的时候,不会影响因为当前调整而再计算,如下。

代码语言:javascript复制
const codeImageWidth = 150   /* 绘制二维码宽度 */
const canvasImageWidth = 85  /* 二维码绘制到canvas的宽度 */
const left = x - 15          /* left 值 */
const top = y - 22           /* top 值 */
const LogoWidth = 15         /* 二维码logo宽度 */

const offset = (canvasImageWidth - LogoWidth) / 2 /* 偏移量 */

4 完事具备,生成海报图片,转发好友

我们已经跑完整个流程。就剩下最后一步,生成海报图片,转发图片了。生成海报可以用微信小程序canvas中的canvasToTempFilePath生成图片路径,然后通过previewImage方法浏览图片,浏览图片时候就可以唤醒微信小程序的分享好友功能了。这里有一点我们应该注意,就是要截取canvas的有效高度。

上代码:

代码语言:javascript复制
/* 生成海报 */
makePc(node) {
    const {
        startTop,    /* 截取canvas画布的顶部 */
        endTop,      /* 截取canvas画布的底部 */
        windowWidth  /* 屏幕宽度 */
    } = this
    const _this = this
    Taro.canvasToTempFilePath({
        x: 0,
        y: startTop,
        width: windowWidth,
        height: endTop - startTop,
        destWidth: windowWidth * 3,
        destHeight: (endTop - startTop) * 3,
        canvas: node,
        success: function (res) {
            Taro.hideLoading()
            Taro.previewImage({
                urls: [res.tempFilePath]
            })

        }
    })
}
canvasToTempFilePath 注意事项

还是回到最初的那个问题,在调用 canvasToTempFilePath 方法的时候,新老 api 传递的参数不同。

在老版本API中 ,通过createCanvasContext 方式绘制的canvascanvasToTempFilePath 的配置属性canvas, 微信开发者文档是这么解释的 canvas 画布标识,传入 canvas 组件实例 (canvas type="2d" 时使用该属性), 也就是canvas上下文context

但是我们用的是新版本 ,通过 getContext 方式绘制的canvas ,当我们传入的是context,竟然没有效果,what? 还有这种事,难道是微信开发者文档出现了问题吗?后来发现在这种方式下,传入的是通过 query.select获取的canvasnode节点,真是坑不少啊~~~。一口老血都要喷出来了

5 效果总览

五 总结

在做这个功能的时候,真是遇到了很多坑,甚至于有一种欲哭无泪的感觉,不过踩着坑一路走来,确实也收获蛮多。

项目地址

我把整个生成海报的全流程的demo项目,上传到github, 感兴趣的同学,可以看看,尤其是正在做这个功能的同学们,加油~

0 人点赞