小程序码生成
Published on 2022-05-20 16:20 in 分类: Miniprogram with dutrmp19
分类: Miniprogram

小程序码生成

扫码时发生了什么

如何获取小程序码

小程序码的本质是,向腾讯的服务器发送一个 get 请求,携带两个参数: page 和 scene。

以获取id 为 1 的问题为例,在本项目中,它被封装成这样一个接口:

method: get
url: server_hostname/api/core/images/suncode/
queries: {
page: 'pages/question/question',
scene: 'question_id=1'
}

后端会返回一个 url,这个 url 粘贴到浏览器中访问就是一张图片。

扫码时发生了什么

扫码时,会根据发送请求时的 page 参数决定进入哪一个页面,

众所周知,onLoad(options),options 可以解析进入的参数

扫码进入时,options将携带一个 scene 参数,下面是 onLoad 开头的部分代码.

  1. 通过 scene 值是否为 undefined 区分是扫码进入还是普通的页面跳转进入
  2. scene需要 decodeURIComponent,不要问为啥,腾讯就是这样给的
  3. showback 是页面顶部的返回按钮是否显示控制,因为扫码进入肯定不能返回上一个页面,所以置为false,到时候在 wxml中控制
if (options.scene !== undefined) { // 通过 scene 设置 question.id
var scene = decodeURIComponent(options.scene) // 现在,scene='question_id=1'
console.log('dejson scene:', scene)
var qid = scene.split('=')[1]
// console.log("question id:", qid)
this.data.question.id = parseInt(qid)
this.setData({
show_back: false // 是否显示 返回按钮,扫码进入不应该显示,只应该提供到达 index 的那妞
})
} else { // 通过 options 直接设置 question_id
this.data.question.id = options.question_id
}

扫码进入的页面呈现

page.json中,有 "navigationStyle": "custom",,要控制导航栏

还有一个自定义的导航栏组件:

"usingComponents": {
"navBar": "miniprograms-navigation-bar"
},

wxml 中,有

<navBar
title='游迹'
background='#fff'
back="{{show_back}}"
home="{{true}}"
bindback="handlerGobackClick"
bindhome="handlerGohomeClick"
>
</navBar>

navBar 是自定义的组件,用来控制页面的导航栏

canvas 在哪,多大?

此部分略微硬核。

可以分为两个部分介绍,canvas和popup

canvas 放在一个 container 中,

  1. 大小,单位为rpx,写死
  2. position fixed,left 100%,为的是把这个 container 放到用户视图的外面,作画时,canvas会改变,我们不希望用户看到这个过程

坑点:

  1. 不要为canvas设置width和height属性,可能导致拉伸
  2. 不要为 canvas中style 设置 单位为 rpx 的width和height属性,原因同一
  3. 统一使用 100% 来控制 canvas 的大小

popup 十分简单,里面只有 一个 image,src="{{gen_img_temp_path}}",在生成小程序码的过程中,会获取一个 canvas对象,逻辑是

  1. canvas.draw,canvas 上的东西就画好了
  2. canvas.saveToTempFile, 把canvas上的东西保存到一张图片上
  3. 然后我们就能得到一张在内存中的图片的路径,把它显示在 popup 上

坑点:

  1. 不可把 canvas 放到popup中
<!-- 下面是生成的小程序码 -->
<!-- 控制 canvas 大小的container,fixed到屏幕外部 -->
<view style='height: 80%;'>
<view
class="canvas_container"
style="width: 600rpx; height: 970rpx; margin: auto; background-color: gray; margin-top: 10%; position: fixed; left: 100%;"
>
<canvas
type="2d"
id="canvas_box"
style="width: 100%; height: 100%;"
>
</canvas>
</view>
</view>
<van-popup
show="{{show_popup}}"
position="bottom"
custom-style="height: {{popup_height_ratio}};"
bind:close="closePopUp"
close-icon-position="top-right"
round
closeable="{{true}}"
close-icon="close"
>
<!-- 大小和canvas画布一致 -->
<image
src="{{gen_img_temp_path}}"
style='width: 600rpx; height: 970rpx;display: block; margin:90rpx auto 0; box-shadow: 0 0 10rpx #909090; border-radius: 20rpx;'
></image>
<!-- </view> -->
<view
class="image_options"
style="display: flex; justify-content: space-around; margin-top: 20rpx;"
>
<van-button
round
type="info"
bindtap="saveToAlbum"
>保存到相册</van-button>
</view>
</van-popup>

canvas的渲染

最为硬核的部分,看不懂没关系,反正都是 copy 来的

onLoad() 开始创建 context 和 canvas 对象

下面这段代码理解为 选中 canvas 对象,然后执行 this.init(),照抄即可

const query = wx.createSelectorQuery()
query.select('#canvas_box')
.fields({
id: true,
node: true,
size: true
})
.exec(this.init.bind(this))

设置 canvas 大小

就是重新设置 canvas 的大小

// 初始化画布
init(res) {
// console.log('init----', res)
const canvas = res[0].node
console.log('res[0].node:', canvas)
var ctx = canvas.getContext('2d') // 获取上下文,绘图时需要条用此对象
const dpr = wx.getSystemInfoSync().pixelRatio; //获取屏幕的像素比 一般值为2
console.log("canvas default size:")
console.log(res[0].node.width)
console.log(res[0].node.height)
// canvas in node dom height and width:
console.log('res[0].h:', res[0].height)
console.log('res[0].w:', res[0].width)
//新接口需显示设置画布宽高; w*2 h*2,父 tag的大小 变大 dpr 倍
canvas.height = res[0].height * dpr
canvas.width = res[0].width * dpr
console.log("reset canvas size by screen,", "w", canvas.width, "h:", canvas.height)
ctx = canvas.getContext('2d')
this.setData({
canvas,
ctx
});
console.log("after set canvas hw, canvas:", this.data.canvas)
},

特别注意,我们要重设 canvas 的大小,因为canvas 在的父tag单位是 rpx, 不然 canvas 太小

canvas.height = res[0].height * dpr
canvas.width = res[0].width * dpr

过程

下载网络上的 suncode

下面是点击按钮触发的 shareQuestion 函数,主要就是 发送请求,获取 小程序码所在的url,然后 downloadFile,

下载完成,保存下载路径后,调用 renderCanvas 方法

// 1. wx.req
// 2. renderCanvas
shareQuestion: function () {
var that = this
var scene = "question_id%3D" + this.data.question.id // %3D就是等号
var page = "pages/question/question"
var url = utils.server_hostname + "/api/core/images/suncode/?"
url += "scene=" + scene + "&" +
"page=" + page
console.log("request suncode url:", url)
wx.request({
url: url,
method: "GET",
header: {
'content-type': 'application/json',
},
success: function (res) {
// console.log("after get suncode url, res:", res)
var suncode_url = res.data.url
wx.downloadFile({ // 开始下载这张小程序码,放到 临时文件里
url: suncode_url,
success: (result) => {
console.log("download suncode success,res:", result)
var tempSuncodePath = result.tempFilePath
that.setData({
download_temp_suncode: tempSuncodePath
})
that.renderCanvas() // 下载好了图片后,就可以开始画画
},
fail: () => {
console.log("get suncode to temp file failed!")
},
complete: () => {}
});
},
fail(res) {
wx.showToast({
title: 'error: 调用 wxreq 失败!',
icon: 'none'
})
}
})
}

绘制canvas

canvasDraw 是把小程序码画上去的方法,

this.title 是把文字画上去的方法

可以理解为把小程序码和文字滑到canvas上后,调用微信提供的存储 canvas 为 tempFile 方法,它会把canvas存储成一张图片,然后返回图片的路径

然后 popup 弹出,利用 src=xxx 显示这张图片

// 1. canvasDraw
// 2. title
renderCanvas: function () {
this.canvasDraw(this.data.ctx, this.data.canvas).then(res => {
console.log('1', res)
// 向画布载入title的方法
this.title(this.data.ctx, this.data.canvas)
var that = this
console.log('保存canvasId', this.data.canvas._canvasId)
console.log('this.data.canvas:', this.data.canvas)
this.setData({
show_popup: true
})
wx.canvasToTempFilePath({ //将canvas生成图片
canvas: this.data.canvas,
x: 0,
y: 0,
width: this.data.canvas.width,
height: this.data.canvas.width,
destWidth: this.data.canvas.width, //截取canvas的宽度
destHeight: this.data.canvas.height, //截取canvas的高度
success: function (res) {
console.log('生成图片成功:', res)
that.setData({
gen_img_temp_path: res.tempFilePath,
})
},
fail: function (res) {
console.log("保存失败")
console.log(res)
}
}, this)
})
},

绘制小程序码

在此步骤中,主要是 加减法计算小程序码要划在哪个位置,画布左上角为(0, 0),往右走 x 轴,往下走 y轴
把img画到canvas 上要提供四个参数

x: 图片左上角的x坐标
y: 图片左上角的y坐标
img_width:图片绘制宽度
img_height:图片绘制高度

对于 x 的要求,是让他居中,给定 父和子的宽度,居中也容易;对于y的要求,是让它和canvas的顶部保持一定的距离

img_width和 height,要让图片够大,清晰即可

/**
* 将小程序码绘制上去
* @param {*} ctx
* @param {*} canvas
*/
canvasDraw(ctx, canvas) {
return new Promise(res => {
let img = this.data.canvas.createImage(); //创建img对象
img.src = this.data.download_temp_suncode;
console.log("before image load")
img.onload = () => { // 必须等待图片加载完成后,才能开始绘制
console.log("image loaded?:", img.complete); //true
console.log("suncode size: ", "w:", img.width, "h:", img.height)
console.log("ctx size:", "w:", canvas.width, "h:", canvas.height)
var draw_image_width = canvas.height * 0.4
var draw_image_height = draw_image_width
var left_up_draw_x = (canvas.width - draw_image_width) / 2
// 自行控制 padding-top
var left_up_draw_y = (this.data.canvas.height * 0.15)
this.data.ctx.fillStyle = "rgba(255, 255, 255)"; // 设置画笔的颜色
this.data.ctx.fillRect(0, 0, canvas.width, canvas.height) // 整张画布是白色,否则变 png透明
console.log("draw images params: ", left_up_draw_x, left_up_draw_y, draw_image_width, draw_image_height)
this.data.ctx.drawImage(img, left_up_draw_x, left_up_draw_y, draw_image_width, draw_image_height);
// this.data.ctx.strokeRect(left_up_draw_x, left_up_draw_y, draw_image_width, draw_image_height)
// 等待一段时间再返回,因为 draImage没有提供回调方法,要保证一段时间图片画好了
// 再把整个过程返回
setTimeout(() => {
res(true)
}, 100);
};
})
},

绘制文字

十分简单,难点是字体大小需要控制

fillText 需要(文字内容,x轴坐标,y轴坐标,文字宽度),当文字内容超过文字宽度时,画笔会压缩文字

//文字模块,不使用pormise,因为他是最后模块,所有不需要了
title(ctx, canvas) {
ctx.font = 'normal bold 50px sans-serif';
ctx.textAlign = 'center' // 告诉画笔,居中绘制
ctx.fillStyle = "#368aee"; // 蓝色
var firstLine = this.data.question.author.nickname + "的提问:"
ctx.fillText(firstLine, this.data.canvas.width * 0.5, canvas.height * 0.650, canvas.width * 0.8)
ctx.fillStyle = "#368aee"; // 蓝色
var thirdLine = "扫码来 HexPal,查看更多精彩内容"
ctx.fillText(thirdLine, this.data.canvas.width * 0.5, canvas.height * 0.85, canvas.width * 0.8)
ctx.fillStyle = "#ff7f00"; // 橙色
ctx.font = 'normal bold 70px sans-serif';
var secondLine = this.data.question.title
ctx.fillText(secondLine, this.data.canvas.width * 0.5, canvas.height * 0.75, canvas.width)
},

保存图片

把这张在内存里的图片,保存到手机的硬盘上。

这个地方写重复了,因为上一步已经有临时图片路径了,不过既然能跑,就别改了

调用 wx.saveImageToPhotosAlbum 即可

saveToAlbum() {
// 保存到相册
var that = this
var w = that.data.canvas.width
var h = that.data.canvas.height
console.log('保存canvasId', this.data.canvas._canvasId)
wx.canvasToTempFilePath({ //将canvas生成图片
canvas: this.data.canvas,
x: 0,
y: 0,
width: w,
height: h,
destWidth: w * 2, //截取canvas的宽度
destHeight: h * 2, //截取canvas的高度
success: function (res) {
console.log('生成图片成功:', res)
that.setData({
show_popup: false
})
wx.saveImageToPhotosAlbum({ //保存图片到相册
filePath: res.tempFilePath,
success: function (res) {
console.log("保存到相册成功")
wx.showToast({
title: "保存图片成功!",
duration: 2000
})
},
fail: function (res) {
console.log("保存失败")
console.log(res)
}
})
},
}, this)
},

如果您有任何关于文章的建议,欢迎评论或在 GitHub 提 PR

作者:dutrmp19
本文为作者原创,转载请在 文章开头 注明出处:https://www.cnblogs.com/dutrmp19/p/16292726.html
遵循 CC 4.0 BY-SA 版权协议


posted @   dutrmp19  阅读(345)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示