最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 「建议收藏」小程序canvas绘制带二维码海报全流程

    正文概述 掘金(我不是外星人)   2021-02-18   1153

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

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

    一 写在前面

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

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

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

    taro框架遇到的坑

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

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

    小程序canvas遇到的坑

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

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

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

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

    ⑦ canvas怎么绘制base64的图片✅

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

    生成二维码遇到的坑

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

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

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

    2 实现效果

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

    1 两种cavnas获取上下文方式

    我们即将解决的问题

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

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

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

    老版本 createCanvasContext 方式

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

    wxml

    <canvas style="width: 300px; height: 200px;" canvas-id="firstCanvas"></canvas>
    

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

    js中这么写

    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

     <canvas type="2d" id="myCanvas"></canvas>
    

    js

    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,就是上述讲的第二个方案。代码如下:

    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节点,我们可以这么做,

    组件中

    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执行。

    mounted(){
      Taro.nextTick(() => {
          // 获取元素
      })   
    }
    

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

    我们即将解决的问题:

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

    <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绘制带二维码海报全流程

    实际效果:

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

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

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

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

    「建议收藏」小程序canvas绘制带二维码海报全流程

    注意事项

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

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

    <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,如图下示例:

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

    封装获取位置信息方法

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

        /* 获取元素位置 */
        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绘制带二维码海报全流程

    第二步:

    「建议收藏」小程序canvas绘制带二维码海报全流程

    第三步:

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

     /* 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绘制图片。如下

    /* 绘制图片 */
    context.drawImage(url,x,y,width,height,dx,dy,dwidth,dheight)
    

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

    新版本绘制方法

      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 方法。因为整个海报生成过程中,内部会画入多张图片,所以我们单独封装了一个绘制图片的方法。

    /* 绘制图片 */
    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用法,

    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进行缩放

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

    「建议收藏」小程序canvas绘制带二维码海报全流程

    3 绘制层级图片

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

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

    <!-- 头像区 -->
    <image  class="userheadImage"  id="userheadImage"  :src="headImage"  />
    
    
        /*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()重置,否则将无法绘制其他元素。

    效果:

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

    4 绘制多行文本

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

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

           /** 画多行文本
             * @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)
                    }
                }
    
            },
    

    调用

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

    效果

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

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

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

    1 关于二维码库选型

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

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

    weapp-qrcode

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

    使用

    //  将 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属性赋值给图片。我们先介绍一下基本用法。

    下载

    npm install qrcode-base64
    

    使用

    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 写入文件,可以将图片写入系统中。

    const fs = wx.getFileSystemManager()
    
    fs.writeFile(/* 写入文件 */)
    

    封装方法

    封装绘制二维码方法

      /* 生成二维码 */
            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上写一个元素,作为占位,方便我们可以获取二维码的位置。

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

    效果

    「建议收藏」小程序canvas绘制带二维码海报全流程

    扫描成功

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

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

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

    在线调整二维码

    「建议收藏」小程序canvas绘制带二维码海报全流程

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

    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的有效高度。

    「建议收藏」小程序canvas绘制带二维码海报全流程

    上代码:

    /* 生成海报 */
    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 效果总览

    「建议收藏」小程序canvas绘制带二维码海报全流程

    海报效果

    「建议收藏」小程序canvas绘制带二维码海报全流程

    五 总结

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

    项目地址

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

    项目地址请戳这里

    拜年啦

    新的一年,祝大家新的一年心想事成,万事如意,祝福掘金社区越来越好。


    起源地下载网 » 「建议收藏」小程序canvas绘制带二维码海报全流程

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元