欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

「建議收藏」小程序canvas繪制海報(bào)全流程 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開發(fā)/軟件開發(fā)

知識

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏壧峁┍憬莸闹С郑?

您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >

「建議收藏」小程序canvas繪制海報(bào)全流程

發(fā)表時(shí)間:2021-2-20

發(fā)布人:葵宇科技

瀏覽次數(shù):244

接下來,我會把純前端實(shí)現(xiàn)生成海報(bào)全流程給大家講個(gè)明明白白,把我自己遇到的坑,給大家詳細(xì)分享并講解,防止大家遇到相似問題,即使遇到問題,也會有一個(gè)明確的方向,并且吐血建議大家收藏一波,以備不時(shí)之需。(你不能保證以后的需求,沒有類似的吧,有的話,記得翻出來看看)

一 寫在前面

1 canvas繪制帶二維碼的海報(bào),這些坑總有一個(gè)你可能會踩到,我會帶你一步步解決這些坑

技術(shù)選型背景:taro3.0-vue

先來十一個(gè)問題壓壓驚,相信你做繪制海報(bào)過程中,一定會遇到

taro框架遇到的坑

① taro-vue createCanvasContext 獲取canvas實(shí)例無效問題,繪制不出來效果??

② taro-vue 初始化獲取不到canvas上下文怎么辦,完全繪制不出來圖片??

小程序canvas遇到的坑

③ 關(guān)于canvas 寬高以及縮放比問題,繪制的元素變形,畫布的高度真得等于cavans標(biāo)簽設(shè)置的寬高么??

④ canvas怎么繪制疊在一起的兩張圖片,并控制層級??

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

⑥ 如何根據(jù)設(shè)計(jì)稿,精確還原海報(bào)各個(gè)元素位置問題。?

⑦ canvas怎么繪制base64的圖片?

⑧ 如何繪制網(wǎng)絡(luò)的圖片,兩種canvas畫布api,繪制圖片有什么區(qū)別完成?

生成二維碼遇到的坑

⑨ 如何正確選型生成二維碼工具??

⑩ 生成的二維碼,識別不出來怎么辦,?

? 如何繪制二維碼上的logo?


二 實(shí)戰(zhàn)一第一階段:小程序canva初始化

1 兩種cavnas獲取上下文方式

我們即將解決的問題

① taro-vue createCanvasContext 獲取canvas實(shí)例無效問題,繪制不出來效果?

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

微信官網(wǎng)上介紹兩種 canvas 獲取上下文方式,一種是老的api ,一種是新的 api ,接下來我將講解一下這兩api的用法。

老版本 createCanvasContext 方式

createCanvasContext是微信提供的獲取 canvas實(shí)例的老得接口,使用方式如下。

wxml

<canvas style="width: 300px; height: 200px;" canvas-id="firstCanvas">canvas>
復(fù)制代碼

美好的一天從寫一個(gè)hello,world開始。

js中這么寫

onReady(){
    /*  使用 wx.createContext 獲取繪圖上下文 context , firstCanvas 與 canvas 屬性中的canvas-id一一對應(yīng)  */
    const context = wx.createCanvasContext('firstCanvas')
    /* 設(shè)置字體大小 */
    context.setFontSize(20) 
    /* 設(shè)置字體顏色 */
    context.setFillStyle('pink')
    /* 設(shè)置文本內(nèi)容,位置 */
    context.fillText('hello,world', 0, 0)
    context.draw()
}

復(fù)制代碼

老版本是使用createCanvasContext傳入 canvas標(biāo)簽中的 canvas-id屬性,來獲取canvas實(shí)例,老版本的使用起來說實(shí)話,不夠靈活,很多對canvas線條,顏色的設(shè)置,都封裝成方法了,每次改變需要調(diào)用方法。

新版本 getContext 上下文方式

新的方式,則是先通過 createSelectorQuery 獲取 canvas 元素節(jié)點(diǎn), 然后通過 getContext 獲取上下文。

wxml

 <canvas type="2d" id="myCanvas">canvas>
復(fù)制代碼

js

const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({
    node: true,
    size: true
})
.exec((res)=>{
    const { node } = res[0]
    if (!node) return
    /* 獲取 canvas 實(shí)例 */
    const context = node.getContext('2d')
    context.fillStyle = 'pink'
    /* 設(shè)置字體樣式 大小 字體類別 */
    context.font = 'normal 400 12px PingFangSC-Regular',
    context.fillText('hello,world', 0, 0)
})
復(fù)制代碼

這種方式和第一種 createSelectorQuery 方式,在api使用方式上會有微妙的差別,這種寫法更像原生的DOM寫法,設(shè)置顏色,樣式,直接改變context屬性,而不再需要調(diào)用對應(yīng)的api。

taro-vue 使用 canvas

解決問題: ① taro-vue createCanvasContext 獲取canvas實(shí)例無效問題,繪制不出來效果?

因?yàn)槲覀冃〕绦蚣夹g(shù)選擇是 taro-vue2,所以我這里重點(diǎn)將一下,在taro-vue中,目前使用 createCanvasContext 方式獲取 canvas 實(shí)例,繪制畫布從來沒有成功過,即便是createCanvasContext能夠創(chuàng)建上下文,但是任何東西也畫不出來(傳this之類的方案試了一個(gè)遍)。要是問我為什么?實(shí)際我也不知道,只有凹凸實(shí)驗(yàn)室的同學(xué)應(yīng)該更清楚,GitHub上也有issue,希望taro團(tuán)隊(duì)能夠重視起來。

解決方案就是采用最新的api,就是上述講的第二個(gè)方案。代碼如下:

import Taro  from '@tarojs/taro'
const query = Taro.createSelectorQuery()
query.select('#myCanvas')
.fields({
    node: true,
    size: true
})
.exec(res=>{
    //TODO:....
})
復(fù)制代碼

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

在使用taro-vue的過程中,會面臨一個(gè)問題,就是小程序node節(jié)點(diǎn)獲取不到的問題,這個(gè)有可能是小程序本身的生命周期,和vue生命周期混亂造成的。尤其當(dāng)我們選擇的是組件而不是頁面的情況。對于這樣的情況,官方文檔給出了答案。頁面首次渲染完畢時(shí)執(zhí)行,此生命周期在小程序端對應(yīng)小程序頁面的 onReady 生命周期。從此生命周期開始可以使用 createCanvasContext 或 createselectorquery 等 API 訪問真實(shí) DOM。

也就是說如果想要獲取真是dom節(jié)點(diǎn),我們可以這么做,

組件中

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

復(fù)制代碼

尷尬的是,這種情況下,有的時(shí)候會造成 eventCenter.once() 回調(diào)函數(shù)不執(zhí)行的情況,比如說當(dāng)前組件的是收到v-if控制的情況。那么怎么樣解決呢,對于這種情況,我教大家一種解決方案。

我們可以用taro中,通過 Taro.nextTick 方法,將獲取元素的任務(wù)放在下一次nextTick執(zhí)行。

mounted(){
  Taro.nextTick(() => {
      // 獲取元素
  })   
}
復(fù)制代碼
2 初始化 canvas設(shè)置寬高百分比

我們即將解決的問題:

③ 關(guān)于canvas 寬高以及縮放比問題,繪制的元素變形,畫布的高度真得等于cavans標(biāo)簽設(shè)置的寬高么?

<template>
    <view>
        <canvas
            id="myPoster"
            type="2d"
            class="canves"
            :style="canvasStyle"
        />
    view>
<template>
復(fù)制代碼

在這里我們首先要明白二個(gè)概念, 容器寬高: 我們給canvas標(biāo)簽設(shè)置的寬高,就是如上代碼中的 canvasStyle,是canvas容器的寬高。 畫布寬高: 而我們畫布的寬高,在新版本api中,是通過獲取node節(jié)點(diǎn),動態(tài)設(shè)置的node.widthnode.height的值。

我們期望將整個(gè)屏幕作為畫布,對于不同手機(jī),屏幕尺寸都會有差別,所以要動態(tài)獲取設(shè)備的寬高。這里有一個(gè)問題是 容器寬高等于畫布寬高嗎 , 答案是否定的,為什么這么說呢,原因如下 小程序的canvas畫布有一個(gè)原始的畫布寬高,以及縮放比,而且是按照一倍像素來的,當(dāng)我們給canvas容器設(shè)定容器寬高之后,如果沒有對應(yīng)設(shè)置canvas畫布的畫布寬高以及scale,畫出的畫布就會嚴(yán)重的變形,我們用一個(gè)例子來解釋。

比如我們想再畫布上半部分區(qū)域,畫一個(gè)圖片,當(dāng)我們期望正常比例畫 canvas ,如果我們只給cavans標(biāo)簽加寬高,而不給畫布設(shè)置寬高的時(shí)候。會按照原始畫布的寬高比去繪制。期望結(jié)果,畫布充滿屏幕,圖片按照正常比列展示。當(dāng)我們不給 cavnas 畫布設(shè)置畫布寬高 以及縮放比的時(shí)候。會發(fā)生下面的情況。

實(shí)際效果:

所以我們初始化的時(shí)候要給canvas如下操作。這個(gè)在微信的官方文檔中,都有說明。

import Taro, {
    eventCenter,
    getCurrentInstance
} from '@tarojs/taro'

export default {
    
    name:'myPoster',
    data(){
        const {
            windowHeight,
            windowWidth,
            pixelRatio
        } = Taro.getSystemInfoSync() /* 動態(tài)獲取設(shè)備的寬和高  */
       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)
            })
        })
    }
}
復(fù)制代碼

當(dāng)我們設(shè)置好畫布寬高,以及縮放比之后,就能按照正常比列進(jìn)行繪制了。讓我們一起看看設(shè)置完縮放比之后的圖片效果,變成了我們想要的效果。

接下來就是繪制階段。


三 實(shí)戰(zhàn)第二階段: 虛擬點(diǎn)位繪制canvas階段

在講解canvas如何生成海報(bào),完美還原設(shè)計(jì)稿的問題之前,我們應(yīng)該想一個(gè)問題,因?yàn)?code>canvas畫布,畢竟不是 dom模型,可以使用div或者view,通過自定義設(shè)置樣式來進(jìn)行布局。cavnas需要我們畫出元素的布局效果,這里就要精確獲取畫布上每一個(gè)元素相對與畫布的x,y值。那么首先想到的是如何獲取每一個(gè)元素精確的x , y 值。

1 虛擬點(diǎn)位還原實(shí)際設(shè)計(jì)稿

解決問題: ⑥ 如何根據(jù)設(shè)計(jì)稿,精確還原海報(bào)各個(gè)元素位置問題。 針對完美還原設(shè)計(jì)稿的問題,比較靠譜的方案就是,先1:1正常掛在dom元素,然后通過獲取元素的位置,來繪制canvas畫布的元素位置。我們用一幅圖來表示其原理。

注意事項(xiàng)

注意事項(xiàng)1: 選擇正確的元素獲取點(diǎn)

這里打一個(gè)比方,我們在dom元素中可能存在這樣的結(jié)構(gòu)。

<view class="box" >
    <view class="parent" >
        <view class="son" > 這里是將要繪制到canvas中的內(nèi)容。 view>
    view>
view>
復(fù)制代碼

對于上面的結(jié)構(gòu),我們只需要將 son中的內(nèi)容繪制到 canvas 畫布中,那么就有一個(gè)問題,我們要獲取哪一層級的元素信息(left,top,width,height),答案應(yīng)該都能猜到,應(yīng)該是想要繪制的內(nèi)容最近的一層,也就是面的son層級。如果我們選外層,可能收到父元素padding,margin等影響,導(dǎo)致真實(shí)的位置不準(zhǔn)確。

注意事項(xiàng)2: 盡量不要給獲取信息的元素增加 padding marign,如果繪制文本內(nèi)容,盡量容器高度等于文本高度

還有一個(gè)問題,就是盡量不要給需要繪制的元素,增加 padding marign等屬性,如果是繪制純文本,不要設(shè)置lineHeight,如圖下示例:

我們期望在獲取 a 點(diǎn)的位置信息, 但是最終卻獲取 b點(diǎn)的位置信息。如果用 b 點(diǎn)位置來繪制canvas,勢必不能完美還原設(shè)計(jì)稿,所以我們在用這種方式繪制canvas的時(shí)候,應(yīng)該注意這些細(xì)節(jié)問題。

封裝獲取位置信息方法

我們需要繪制海報(bào)上的每一個(gè)點(diǎn)位,首先想到的就是獲取小程序元素位置方法,并封裝該方法。我們用promise來防止深層次的回調(diào),并且方便使用async await語法糖。廢話不多說,一言不合上代碼。

    /* 獲取元素位置 */
    geDomPostion(dom, isAll) {
        return new Promise((resolve) => {
            Taro.createSelectorQuery().select(dom).boundingClientRect(rect => {
                const {
                    top,
                    left
                } = rect
                /* isAll 是否獲取設(shè)備寬高等信息 */
                resolve(isAll ? rect : {
                    top,
                    left
                })
            }).exec()
        })
    },
復(fù)制代碼

小提示:如果用wx原生,或者其他跨端框架mpvue wepy uniapp是的同學(xué),把 Taro 換成 wx 即可。


2 繪制網(wǎng)絡(luò)圖片

繪制網(wǎng)絡(luò)圖片

接下來我們要解決的問題: ⑨ 如何繪制網(wǎng)絡(luò)的圖片,兩種通過canvas畫布api,繪制圖片有什么區(qū)別?

我們在用canvas繪制圖片的時(shí)候,對于本地圖片可以直接通過canvas提供的drawImage進(jìn)行繪制,但是對于網(wǎng)絡(luò)圖片是不能這么繪制的,我們首先需要通過getImageInfo來獲取圖片的臨時(shí)路徑。用getImageInfo繪制網(wǎng)絡(luò)資源的時(shí)候請注意配置一下合法的下載域名,要不然我們是無法成功獲取圖片信息的。我們首先需要在小程序后臺配置downloadFile合法域名。

具體步驟如下: 第一步:

第二步:

第三步:

接下來我們要做的就是讀取圖片的臨時(shí)路徑,繪制到canvas畫布上來。

 /* backGroundImageUrl 是我們要畫的網(wǎng)絡(luò)圖片的地址  */
 this.getImageInfo(this.backGroundImageUrl).then(res=>{
      const {
        width,   /* 寬度 */
        height,  
        path     /* 臨時(shí)路徑 */
      } = 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()
 })
復(fù)制代碼

this.drawImage 是我們封裝好的方法,之前說過對于小程序獲取 context兩種接口方式,兩種方式繪制canvas圖片,有一些差別,我們馬上道來。

新老接口繪制圖片的區(qū)別

老版本繪制方法

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

/* 繪制圖片 */
context.drawImage(url,x,y,width,height,dx,dy,dwidth,dheight)
復(fù)制代碼

當(dāng)時(shí)我們項(xiàng)目用的是第二種新api getContext當(dāng)時(shí)獲取上下文,所以在圖片繪制方式上,會有所改變。

新版本繪制方法

  const image = node.createImage()
  image.src = http://www.wxapp-union.com/url
  image.onload = () => {
    context.drawImage(image,x,y,width,height,dx,dy,dwidth,dheight)
  }
復(fù)制代碼

用新版本的API 繪制圖片的同學(xué)請注意,這個(gè)onload回調(diào)是在圖片加載完成時(shí)候執(zhí)行的,所以說明是異步的。還有一個(gè)注意的地方,相比老版本的 drawImage 第一個(gè)參數(shù)是圖片的路徑,而新版本的drawImage第一個(gè)參數(shù)是image元素。

封裝繪制圖片方法

剛才在繪制網(wǎng)絡(luò)圖片最后一步,我們調(diào)用了 this.drawImage 方法。因?yàn)檎麄€(gè)海報(bào)生成過程中,內(nèi)部會畫入多張圖片,所以我們單獨(dú)封裝了一個(gè)繪制圖片的方法。

/* 繪制圖片 */
drawImage(context, node, url, ...arg) {
    return new Promise((resolve) => {
        const image = node.createImage()
        image.src = http://www.wxapp-union.com/url
        image.onload = () => {
            context.drawImage(image, ...arg)
            resolve()
        }
    })
},
復(fù)制代碼

這樣我們就可以通過,async,await判斷圖片是否加載完成。

簡介 context.drawImage

我這里簡單給大家介紹一下context.drawImage用法,

CanvasContext.drawImage(imageResource / dom, sx,  sy,  sWidth,  sHeight,  dx,  dy,  dWidth,  dHeight)
復(fù)制代碼

繪制圖像到畫布,第一個(gè)參數(shù),在老api中代表路徑,在新版本api中代表imagDom元素,

sx 需要繪制到畫布中的,imageResource / dom 的矩形(裁剪)選擇框的左上角 x 坐標(biāo)

sy 需要繪制到畫布中的,imageResource / dom 的矩形(裁剪)選擇框的左上角 y 坐標(biāo)

sWidth 需要繪制到畫布中的,imageResource / dom 的矩形(裁剪)選擇框的寬度

sHeight 需要繪制到畫布中的,imageResource / dom 的矩形(裁剪)選擇框的高度

dx imageResource的左上角在目標(biāo) canvas 上 x 軸的位置

dy imageResource的左上角在目標(biāo) canvas 上 y 軸的位置

dWidth 在目標(biāo)畫布上繪制imageResource的寬度,允許對繪制的imageResource進(jìn)行縮放

dHeight 在目標(biāo)畫布上繪制imageResource的高度,允許對繪制的imageResource進(jìn)行縮放

我們用一幅圖表示各個(gè)屬性的對應(yīng)什么。

3 繪制層級圖片

解決問題: ④ canvas怎么繪制疊在一起的兩張圖片,并控制層級?

如果我們繪制疊在一起的兩張圖片,需要我們做一些什么樣的工作呢?首先想到的是層級問題,我們期望背景圖片放在下面,例如頭像之類的圖片放在上面,但是在畫布中沒有控制zIndex層級的屬性,那么怎么樣處理這個(gè)問題呢 ?答案是實(shí)際在canvas中,繪制的先后順序 就是畫布層級順序,后畫的在先畫的上層,那么對于這種層級問題呢,我們只要保證層級高的元素后畫,層級低的元素先畫就可以完美解決,接下來我們在海報(bào)中,畫上頭像,文字等信息。


<image  class="userheadImage"  id="userheadImage"  :src="headImage"  />

復(fù)制代碼
    /*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')
復(fù)制代碼

在我們使用context.clip()之后,記得使用context.restore()重置,否則將無法繪制其他元素。

效果:

我們完美解決了片文本的層級問題,接下來,我們就要繪制海報(bào)的主要的內(nèi)容了。在我們繪制海報(bào)的時(shí)候,可能會遇到多行文本的情況,那么多對多行文本,我們是怎么解決的呢?

4 繪制多行文本

解決問題:⑤ 如何用canvas繪制,多行文本?

canvas畫的文本,并不能像我們的dom元素下的文本一樣,可以自動換行,我們?nèi)绾芜€原,多行文本的效果呢。這這里教大家一種方法,我們可以一個(gè)一個(gè)字的繪制到canvas中,然后把每個(gè)字的寬度相加,如果總寬度大于容器的寬度,那么就另外起一行,增加每一行的高度,從頭開始畫。,我們直接上代碼。

       /** 畫多行文本
         * @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;
            /* 設(shè)置文字樣式 */
            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)
                }
            }

        },
復(fù)制代碼

調(diào)用

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

四 實(shí)戰(zhàn)第三階段: 生成二維碼

接下來我們做的是繪制二維碼,繪制二維碼過程,筆者踩了不少的坑,尤其taro-vue不支持createCanvasContext方式,希望我能用自己踩的坑,讓大家避開相同的錯(cuò)誤,避免大家少走很多彎路。繪制二維碼實(shí)際并沒有想象的復(fù)雜,實(shí)際就是將鏈接轉(zhuǎn)換成二維碼,讓手機(jī)掃碼或者長按可以識別即可,雖然原理很簡單,但是還是有很多注意的細(xì)節(jié)。

繪制二維碼無異于二種方式,第一種方式就是用canvas畫出來。第二種將鏈接轉(zhuǎn)成base64的鏈接,然后讓圖片展示鏈接。 接下來我們針對這兩種方式,進(jìn)行二維碼庫的技術(shù)選型。

1 關(guān)于二維碼庫選型

解決問題 ⑨ 如何正確選型生成二維碼工具?

形成二維碼的過程,我們肯定不能手?jǐn)]算法,因?yàn)榧幢阄覀兡苁謹(jǐn)]出來,也會占用大量時(shí)間,還會有很多bug,因?yàn)楝F(xiàn)在生成二維碼的生態(tài)已經(jīng)很健全了,比如 qrcode.js等等都是非常不錯(cuò)的,但是唯一不好的是不支持小程序端。我這里介紹幾個(gè)二維碼的庫

weapp-qrcode

對于比如短鏈接,不必拼寫很長的參數(shù),這種情況用 weapp-qrcode 綽綽有余。這種方式是基于第一種用canvas繪制的。而且是采用老版本的api , 這樣的話就有一個(gè)問題,如果像用新的 getContext 方式,就需要把源碼下載下來,然后改動一下源碼,讓它支持 getContext 這種方式。我們來簡介一下 weapp-qrcode的使用。

使用

//  將 dist 目錄下,weapp.qrcode.esm.js 復(fù)制到項(xiàng)目目錄中  如果用 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
  }
})
復(fù)制代碼

結(jié)果

這種方式下,最后確實(shí)成功了,因?yàn)樵谧?code>demo的時(shí)候,我用的是github短鏈接。但是一回歸筆者公司的項(xiàng)目,很長的鏈接,奈何生成的二維碼特別密集,手機(jī)根本識別不出來,無奈前功盡棄了,只能換其他的技術(shù)方案,所以筆者選擇了第二種比較穩(wěn)的方式,形成base64文件。

qrcode-base64

qrcode-base64 是將二維碼的鏈接,轉(zhuǎn)成base64的鏈接,并把這個(gè)鏈接作為src屬性賦值給圖片。我們先介紹一下基本用法。

下載

npm install qrcode-base64
復(fù)制代碼

使用

import QR from 'qrcode-base64'

var imgData = http://www.wxapp-union.com/QR.drawImg(this.data.codeText, {
    typeNumber: 4,
    errorCorrectLevel: 'M',
    size: 500
})
// 返回輸出base64編碼imgData
復(fù)制代碼

如上述代碼塊所示,imgData就是生成的base64鏈接,我們可以直接把它作為圖片的src,然后讓canvas將圖片繪制到我們的海報(bào)中去,但是又來了一個(gè)問題,canvas是不支持繪制base64的鏈接圖片的,真機(jī)上沒有任何效果,真實(shí)一步十個(gè)坑啊,我們還得想辦法解決這個(gè)問題。

2 canvas 繪制 base64圖片

解決問題 ⑦ canvas怎么繪制base64的圖片

對于上面說到的canvas不支持base64的圖片,那么我們還要把二維碼繪制到海報(bào)中,那么并不是沒有辦法,我們可以用小程序提供的文件系統(tǒng)來解決問題。

小程序文件系統(tǒng)

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

writeFile 寫入文件,可以將圖片寫入系統(tǒng)中。

const fs = wx.getFileSystemManager()

fs.writeFile(/* 寫入文件 */)
復(fù)制代碼

封裝方法

封裝繪制二維碼方法

  /* 生成二維碼 */
        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
                })
                /* 創(chuàng)建讀寫流 */
                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),  /* 數(shù)據(jù)流 */
                    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()

                    }
                })
            })

        },
復(fù)制代碼

如上所示我們完成了二維碼的繪制。讓我們來看一下如何使用。

使用

我們在wxml上寫一個(gè)元素,作為占位,方便我們可以獲取二維碼的位置。

<view id="qrCode" class="store-uscode" />
復(fù)制代碼
/*TODO: 第四步:繪制二維碼 */
const qrCode = await this.geDomPostion('#qrCode')
await this.drawCode(context, node, qrCode.left - 20, qrCode.top - this.cavnsOffsetop)
復(fù)制代碼
3 調(diào)試二維碼大小,如何讓二維碼可以識別,繪制二維碼logo

解決問題:⑩ 生成的二維碼,識別不出來怎么辦。

有的時(shí)候我們展示的二維碼比較小的時(shí)候,因?yàn)樯珘K太密,手機(jī)也會有無法識別的情況。那么我們如何調(diào)整二維碼,有能讓頁面盡量高保真的還原設(shè)計(jì)稿呢,這里教大家一個(gè)小技巧,可以去先去二維碼生成網(wǎng)站,先適配手機(jī)可以識別的最佳比例,避免識別不出來的情況。推薦網(wǎng)站:草料二維碼 https://cli.im/ 我們可以在線調(diào)試二維碼的像素,和 logo的大小,直到調(diào)整出,能夠符合設(shè)計(jì)的最佳大小。

在線調(diào)整二維碼

微調(diào)整 有的時(shí)候,我們需要對二維碼大小進(jìn)行微調(diào)整,我這里建議在調(diào)試階段,建立起常量控制,并調(diào)整寫好調(diào)整方法公式。這樣做的好處是,每當(dāng)我們作出微調(diào)整的時(shí)候,不會影響因?yàn)楫?dāng)前調(diào)整而再計(jì)算,如下。

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 /* 偏移量 */
復(fù)制代碼

4 完事具備,生成海報(bào)圖片,轉(zhuǎn)發(fā)好友

我們已經(jīng)跑完整個(gè)流程。就剩下最后一步,生成海報(bào)圖片,轉(zhuǎn)發(fā)圖片了。生成海報(bào)可以用微信小程序canvas中的canvasToTempFilePath生成圖片路徑,然后通過previewImage方法瀏覽圖片,瀏覽圖片時(shí)候就可以喚醒微信小程序的分享好友功能了。這里有一點(diǎn)我們應(yīng)該注意,就是要截取canvas的有效高度。

上代碼:

/* 生成海報(bào) */
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]
            })

        }
    })
}
復(fù)制代碼

canvasToTempFilePath 注意事項(xiàng)

還是回到最初的那個(gè)問題,在調(diào)用 canvasToTempFilePath 方法的時(shí)候,新老 api 傳遞的參數(shù)不同。

在老版本API中 ,通過createCanvasContext 方式繪制的canvas ,canvasToTempFilePath 的配置屬性canvas, 微信開發(fā)者文檔是這么解釋的 canvas 畫布標(biāo)識,傳入 canvas 組件實(shí)例 (canvas type="2d" 時(shí)使用該屬性), 也就是canvas上下文context

但是我們用的是新版本 ,通過 getContext 方式繪制的canvas ,當(dāng)我們傳入的是context,竟然沒有效果,what? 還有這種事,難道是微信開發(fā)者文檔出現(xiàn)了問題嗎?后來發(fā)現(xiàn)在這種方式下,傳入的是通過 query.select獲取的canvasnode節(jié)點(diǎn),真是坑不少啊~~~。一口老血都要噴出來了

五 總結(jié)

在做這個(gè)功能的時(shí)候,真是遇到了很多坑,甚至于有一種欲哭無淚的感覺,不過踩著坑一路走來,確實(shí)也收獲蠻多。

相關(guān)案例查看更多