小程序码生成
扫码时发生了什么
如何获取小程序码
小程序码的本质是,向腾讯的服务器发送一个 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)
  },

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号