小程序码生成
扫码时发生了什么
如何获取小程序码
小程序码的本质是,向腾讯的服务器发送一个 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 开头的部分代码.
- 通过 scene 值是否为 undefined 区分是扫码进入还是普通的页面跳转进入
- scene需要
decodeURIComponent
,不要问为啥,腾讯就是这样给的 - 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 中,
- 大小,单位为rpx,写死
- position fixed,left 100%,为的是把这个 container 放到用户视图的外面,作画时,canvas会改变,我们不希望用户看到这个过程
坑点:
- 不要为canvas设置width和height属性,可能导致拉伸
- 不要为 canvas中style 设置 单位为 rpx 的width和height属性,原因同一
- 统一使用 100% 来控制 canvas 的大小
popup 十分简单,里面只有 一个 image,src="{{gen_img_temp_path}}"
,在生成小程序码的过程中,会获取一个 canvas对象,逻辑是
- canvas.draw,canvas 上的东西就画好了
- canvas.saveToTempFile, 把canvas上的东西保存到一张图片上
- 然后我们就能得到一张在内存中的图片的路径,把它显示在 popup 上
坑点:
- 不可把 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 版权协议
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!