接下来,我会把纯前端实现生成海报全流程给大家讲个明明白白,把我自己遇到的坑,给大家详细分享并讲解,防止大家遇到相似问题,即使遇到问题,也会有一个明确的方向,并且吐血建议大家收藏一波,以备不时之需。(你不能保证以后的需求,没有类似的吧,有的话,记得翻出来看看)
长路漫漫,总得有人敢于迈出踩坑的第一步,我想我就是那个😂😂,希望大家能踏着我冒着抬上救护车危险填平的坑,通向小程序生成海报胜利之路。送人玫瑰,手留余香,阅读的朋友可以给笔者 点赞,关注一波 陆续更新超干,超硬核的前端文章。
一 写在前面
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
<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.width
和 node.height
的值。
我们期望将整个屏幕作为画布,对于不同手机,屏幕尺寸都会有差别,所以要动态获取设备的宽高。这里有一个问题是 容器宽高等于画布宽高吗 , 答案是否定的,为什么这么说呢,原因如下 小程序的canvas
画布有一个原始的画布宽高,以及缩放比,而且是按照一倍像素来的,当我们给canvas
容器设定容器宽高之后,如果没有对应设置canvas
画布的画布宽高以及scale
,画出的画布就会严重的变形,我们用一个例子来解释。
比如我们想再画布上半部分区域,画一个图片,当我们期望正常比例画 canvas
,如果我们只给cavans
标签加宽高,而不给画布设置宽高的时候。会按照原始画布的宽高比去绘制。
期望结果,画布充满屏幕,图片按照正常比列展示。当我们不给 cavnas
画布设置画布宽高 以及缩放比的时候。会发生下面的情况。
实际效果:
所以我们初始化的时候要给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
画布,毕竟不是 dom
模型,可以使用div
或者view
,通过自定义设置样式来进行布局。cavnas
需要我们画出元素的布局效果,这里就要精确获取画布上每一个元素相对与画布的x,y值。那么首先想到的是如何获取每一个元素精确的x , y 值。
1 虚拟点位还原实际设计稿
解决问题: ⑥ 如何根据设计稿,精确还原海报各个元素位置问题。
针对完美还原设计稿的问题,比较靠谱的方案就是,先1:1正常挂在dom
元素,然后通过获取元素的位置,来绘制canvas
画布的元素位置。我们用一幅图来表示其原理。
注意事项
注意事项1: 选择正确的元素获取点
这里打一个比方,我们在dom
元素中可能存在这样的结构。
<view class="box" >
<view class="parent" >
<view class="son" > 这里是将要绘制到canvas中的内容。 </view>
</view>
</view>
对于上面的结构,我们只需要将 son
中的内容绘制到 canvas
画布中,那么就有一个问题,我们要获取哪一层级的元素信息(left,top,width,height
),答案应该都能猜到,应该是想要绘制的内容最近的一层,也就是面的son
层级。如果我们选外层,可能收到父元素padding
,margin
等影响,导致真实的位置不准确。
注意事项2: 尽量不要给获取信息的元素增加 padding
marign
,如果绘制文本内容,尽量容器高度等于文本高度
还有一个问题,就是尽量不要给需要绘制的元素,增加 padding
marign
等属性,如果是绘制纯文本,不要设置lineHeight
,如图下示例:
我们期望在获取 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
画布上来。
/* 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进行缩放
我们用一幅图表示各个属性的对应什么。
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()
重置,否则将无法绘制其他元素。
效果:
我们完美解决了片文本的层级问题,接下来,我们就要绘制海报的主要的内容了。在我们绘制海报的时候,可能会遇到多行文本的情况,那么多对多行文本,我们是怎么解决的呢?
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)
效果
四 实战第三阶段: 生成二维码
接下来我们做的是绘制二维码,绘制二维码过程,笔者踩了不少的坑,尤其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
的使用。
使用
// 将 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)
效果
扫描成功
3 调试二维码大小,如何让二维码可以识别,绘制二维码logo
解决问题:⑩ 生成的二维码,识别不出来怎么办。
有的时候我们展示的二维码比较小的时候,因为色块太密,手机也会有无法识别的情况。那么我们如何调整二维码,有能让页面尽量高保真的还原设计稿呢,这里教大家一个小技巧,可以去先去二维码生成网站,先适配手机可以识别的最佳比例,避免识别不出来的情况。推荐网站:草料二维码 :https://cli.im/
我们可以在线调试二维码的像素,和 logo的大小,直到调整出,能够符合设计的最佳大小。
在线调整二维码
微调整
有的时候,我们需要对二维码大小进行微调整,我这里建议在调试阶段,建立起常量控制,并调整写好调整方法或公式。这样做的好处是,每当我们作出微调整的时候,不会影响因为当前调整而再计算,如下。
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
的有效高度。
上代码:
/* 生成海报 */
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
方式绘制的canvas
,canvasToTempFilePath
的配置属性canvas
, 微信开发者文档是这么解释的 canvas
画布标识,传入 canvas
组件实例 (canvas type="2d" 时使用该属性), 也就是canvas
上下文context
。
但是我们用的是新版本 ,通过 getContext
方式绘制的canvas
,当我们传入的是context
,竟然没有效果,what? 还有这种事,难道是微信开发者文档出现了问题吗?后来发现在这种方式下,传入的是通过query.select
获取的canvas
的node
节点,真是坑不少啊~~~。一口老血都要喷出来了
5 效果总览
五 总结
在做这个功能的时候,真是遇到了很多坑,甚至于有一种欲哭无泪的感觉,不过踩着坑一路走来,确实也收获蛮多。
项目地址
我把整个生成海报的全流程的demo
项目,上传到github
, 感兴趣的同学,可以看看,尤其是正在做这个功能的同学们,加油~
拜年啦
新的一年,祝大家新的一年心想事成,万事如意,祝福掘金社区越来越好。