This is the fourth day of my participation in the August More text Challenge. For details, see: August More Text Challenge


background

Today, when I read the chat record of Nugget Jam, I found that the poster of the annual creator contest last year was manually generated by the little brothers and sisters of design. I happen to be working on a wechat project recently, which is to write a poster demo for August based on the poster of Nugget Jam, so as to free the hands of the little sisters of design for the next activity.

As shown in the figure below, this poster of the annual Creator Contest is actually the type that programmers like. All the positions that need to be filled in dynamically are reserved with enough blank space. Draw a background image and then draw text in the corresponding coordinate position.

I want to have more technical points. Combining with the demand of wechat fission currently under development and the activity of August updating, I designed a poster for the activity of August updating. Let’s implement it with hand exercises.

Hand to realize

1. Demand analysis

After getting the design drawing, let’s first analyze what drawing function points are involved (since the next drawing will overwrite the previous one, similar to the layout on the Z-index layer, so let’s first draw the lower level, such as the background, and then draw it from top down and left to right) :

  1. draw540 * 900On a white background;
  2. Drawing on the top of540 * 650The head of the figure;
  3. drawI am attending XXXThese words;
  4. Draw dividing lines;
  5. Draw avatar, nickname, more text days;
  6. Drawing two-dimensional code or small program code;
  7. Draw bottomLong press to viewremind

2. Code implementation

1. Create canvas container

The first step is to create a Canvas container to draw the poster. Here is a little trick. If the Canvas container is directly displayed, it will need to constantly calculate the pixel ratio and unit conversion (RPX to PX) when drawing. So we can move the Canvas container out of sight of the page using the positioning layout, place the image tag in the position of the page, and SRC performs the address of the image generated by the Canvas. In this way, the size of the Canvas container can be set to the same size as the design drawing, and the image tag can adapt to the unit conversion itself.

index.wxml

<view class="share-box">
  <view class="main">
    <view class="canvas-box">
      <image src="{{imgSrc}}"></image>
    </view>
    <canvas canvas-id="shareCanvas" style="width: 540px; height:900px; position: fixed; top: -10000px;" ></canvas>
    <view class="btn-box">
      <view class="btn" bindtap="download">Save the picture</view>
    </view>
  </view>
</view>
Copy the code

index.wxss

.share-box {
  background: rgba(14.13.13.8);
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 100;
}
.main {
  position: relative;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 101;
}
.canvas-box {
  width: 540rpx;
  height: 900rpx;
  position: fixed;
  top: 100rpx;
  left: 50%;
  margin-left: -270rpx;
  box-shadow: 0rpx 5rpx 10rpx 0rpx rgba(0.12.32.0.17);
  z-index: 9999;
}
.canvas-box image {
  width: 100%;
  height: 100%;
}
.btn-box{
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 150rpx;
  background-color: #fff;
  padding-top: 32rpx;
}
.btn{
  width: 686rpx;
  height: 90rpx;
  margin: 0 auto;
  background: linear-gradient(180deg.#8EADE9 0%.#4965B3 100%);
  border-radius: 12rpx;
  font-size: 36rpx;
  font-weight: 500;
  color: #FFFFFF;
  line-height: 90rpx;
  text-align: center;
}
Copy the code

index.js

Page({
  data: {
    imgSrc: "",},onLoad() {
    this.init()
  },
  init() {
    wx.showLoading({
      title: 'is generating... ',})const ctx = wx.createCanvasContext('shareCanvas')
    ctx.draw(false.() = > {
      this.canvasToImage()
    })
  },
  canvasToImage() {
    wx.canvasToTempFilePath({
      canvasId: 'shareCanvas'.success: res= > {
        wx.hideLoading()
        this.setData({
          imgSrc: res.tempFilePath
        })
      }
    })
  },
  / / save
  download() {
    wx.showLoading({
      title: 'Saving'
    });
    wx.saveImageToPhotosAlbum({
      filePath: this.data.imgSrc,
      success: function () {
        wx.showToast({
          title: 'Saved successfully'
        });
      },
      fail: function (e) {
        wx.showToast({
          title: 'Save failed'
        });
      },
      complete: function () { wx.hideLoading() } }); }})Copy the code

Wx.canvastotempfilepath () generates an image of the current Canvas container contents, called in the callback to the draw() method. You get a page like this:

Wx. SaveImageToPhotosAlbum () images can be saved to the photo album.

2. Draw540 * 900On a white background

SetFillStyle () sets the fill color to # FFF fillRect() to draw a 540 x 900 rectangle starting from the coordinate (0,0)

    ctx.setFillStyle("#fff")
    ctx.fillRect(0, 0, 540, 900)
Copy the code
3. Draw the top540 * 650The head of the figure
ctx.drawImage(".. /.. /img/bg.png", 0, 0, 540, 650)Copy the code

There is a problem here, because the small program 2M limit, so the image is basically network resources, drawImage() to draw network images must first getImageInfo()/downloadFile() to download the network image down, While getImageInfo()/downloadFile() is an asynchronous operation, loading too many images can lead to callback hell, so I used promising.all() to wrap the network load. Ps :getImageInfo()/downloadFile() operation network resources need to configure a small program in the background downloadFile legitimate domain name, I use a rich text image path, so configure the nugget domain name.

  getImgInfo: src= > {
    return new Promise((resolve, reject) = > {
      wx.getImageInfo({
        src: src,
        success: resolve,
        fail: reject
      })
    })
  },
  init() {
    wx.showLoading({
      title: 'is generating... ',})Promise.all([
      this.getImgInfo("https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dcb71be2634641849853eef58675e9c8~tplv-k3u1fbpfcp-watermark.image"),
    ]).then(res= > {
      const ctx = wx.createCanvasContext('shareCanvas')
      // Draw the background color
      ctx.setFillStyle("#fff")
      ctx.fillRect(0.0.540.900)
      
      // Draw the header
      ctx.drawImage(res[0].path, 0.0.540.650)

      ctx.draw(false.() = > {
        this.canvasToImage()
      })
    })
  },
Copy the code

This completes our first big step, resulting in the following picture:

4. DrawI am attending XXXThese words
ctx.setTextBaseline('top') ctx.setTextAlign('left') ctx.setFillStyle('#333') ctx.setFontSize(26) Ctx. fillText(" I'm at the Nuggets' August update event ", 30, 680) , 30, 710)Copy the code

setTextBaseline()Sets the vertical alignment of text. The optional value istop,bottom,middleandnormal. The differences between the four are as follows :(all are drawn at the same y-base)We can set it to 0 when we set ittopThe advantage of this is that the distance from the top of the text on the design drawing is the same as the y-base of the drawing.

setTextAlign()Set horizontal text alignment. The optional value isleft,centerandright. The differences between the three are as follows :(all are drawn at the same x-base)We can set it to 0 when we set itleftThe advantage of this is that the distance to the left of the text on the design drawing is the x-base of the drawing. If you want to center text horizontally, you can set the alignment tocenter, and then draw the X-axis position at 1/2 of the total width.setFontSize()Set the font size (px by default).fillText()Start drawing text at the coordinate position (30px, 680px).

5. Draw the dividing line
      ctx.moveTo(30, 750)
      ctx.lineTo(510, 750)
      ctx.setStrokeStyle('#eeeeee')
      ctx.stroke()
Copy the code

MoveTo () creates the starting point of the path at (30px,750px). LineTo () adds a new point at (510px,750px). SetStrokeStyle () sets the color of the stroke. Stroke () plots the path in the order of the points.

6. Draw profile pictures, nicknames, and days
ctx.save() ctx.setFillStyle('#fff') ctx.beginPath() ctx.arc(70, 800, 40, 0, 2 * Math.PI) ctx.clip() ctx.drawImage(res[1].path, 30, 760, 80, 80) ctx.restore() ctx.font = 'normal bold 26px sans-serif'; Ctx. fillText(" on 5 ", 130, 770) ctx.font = 'normal normal 20px sans-serif'; Ctx.setfillstyle ('#616165') ctx.fillText(' more text ', 130, 60) let wordWidth = ctx.measuretext (" #333 ").width ctx.setstyle ('#333') ctx.fillText("4", 130 + wordWidth + 5, 810) let numWidth = CTX. MeasureText (" 4 "). The width CTX. SetFillStyle (' # 616165) CTX. FillText (" day ", 130 + wordWidth + 5 + numWidth + 5, 810)Copy the code

To draw a circular head, Canvas does not support direct image manipulation, so we have to use other methods: draw the circular Canvas area first, and draw the image in this area. Use the save() method to save the context, then beginPath() to create a new path, and the arc() method to draw a 40px radius circle at the center coordinates of (70px,800px), and then clip(), leaving only the circle area. All ([]) and drawImage. Since the circle starts at the center of the circle and the picture starts at the upper left corner, the starting point of the picture should be (center x distance-radius, center y distance-radius), and the width and height should be the diameter of the circle. When the drawing is complete, the restore() method restores the context we just saved so that we can continue drawing. Look at the nickname these words are bold, so far only found to achieve bold font Settings, other bug. Here, because xx is a variable with a variable width, the measureText() method is called to get the width of the content to be drawn, so the x distance to draw the word “day” = the x coordinate of “already text” + the width of “already text” + the spacing between the left and right words of “xx” + the width of “xx”.

7. Draw qr codes and bottom reminders

Two new web images in promise.all ()

this.getImgInfo("https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8d94a69ca49249278f95c2391cb5dc7d~tplv-k3u1fbpfcp-wate Rmark.image "),// two-dimensional code or small program code this.getImgInfo("https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45e02d3d598e4fe8ab83f30e25fbc08b~tplv-k3u1fbpfcp-wate rmark.image")//iconCopy the code
ctx.drawImage(res[2].path, 410, 750, 100, 100) ctx.drawImage(res[3].path,30, 860, 20, 20) ctx.setfillStyle ('#AAABAD') ctx.filltext (" long press to see ", 58, 860)Copy the code
8. Run result

At this point, the small program to draw the poster is over. You can use your own personal data to generate your own wechat fission pictures.

conclusion

1. UseimageInstead ofCanvasDisplay to the page, you can avoidCanvasUnit conversion problem;
2.canvasToTempFilePath()Must be indraw()Callback inside;
3.drawImage()Used when drawing web imagesgetImageInfo()/downloadFile()Download the pictures from the Internet first;
4.getImageInfo()/downloadFile()When downloading network pictures, you need to configure the security domain name. The development environment can first use the wechat developer tool to check not to verify the legitimate domain name;
5. UsePromise.all()Fix callback hell;
Set 6.CanvasThe alignment mode isSetTextBaseline (' top '), setTextAlign (' left ')In this way, the distance coordinates of the elements (x,y) to be drawn are the positions of the elements on the design drawing;
7. Draw text level and center can be setsetTextAlign('center').fillText()The x position of is half of the total width;
8. Draw a circular head you can draw a circular area first and draw pictures on the circular area;
9.setFillStyle()Set the text color,setFontSize()Set text size,font = 'normal bold 26px sans-serif'Realize text bold;
10.measureText()You can get the width of the text content;

Finally, attach the full JS code:

Page({
  data: {
    imgSrc: "",},onLoad() {
    this.init()
  },
  getImgInfo: src= > {
    return new Promise((resolve, reject) = > {
      wx.getImageInfo({
        src: src,
        success: resolve,
        fail: reject
      })
    })
  },
  init() {
    wx.showLoading({
      title: 'is generating... ',})Promise.all([
      this.getImgInfo("https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dcb71be2634641849853eef58675e9c8~tplv-k3u1fbpfcp-watermark.image"), / / head
      this.getImgInfo("https://sf6-ttcdn-tos.pstatp.com/img/user-avatar/eccbd6c74379889aee23eff8569c815c~300x300.image"), / / avatar
      this.getImgInfo("https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8d94a69ca49249278f95c2391cb5dc7d~tplv-k3u1fbpfcp-watermark.image"), // Two-dimensional code or small program code
      this.getImgInfo("https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45e02d3d598e4fe8ab83f30e25fbc08b~tplv-k3u1fbpfcp-watermark.image") //icon
    ]).then(res= > {
      const ctx = wx.createCanvasContext('shareCanvas')
      // Draw the background color
      ctx.setFillStyle("#fff")
      ctx.fillRect(0.0.540.900)

      // Draw the header
      ctx.drawImage(res[0].path, 0.0.540.650)

      // Draw text
      ctx.setTextBaseline('top')
      ctx.setTextAlign('left')
      ctx.setFillStyle('# 333')
      ctx.setFontSize(26)
      ctx.fillText("I'm at the Nuggets' August update.".30.680)
      ctx.fillText("Give me a thumbs-up!".30.710)

      // Draw the dividing line
      ctx.moveTo(30.750)
      ctx.lineTo(510.750)
      ctx.strokeStyle = '#eeeeee'
      ctx.stroke()

      // Draw profile picture and personal information
      ctx.save()
      ctx.setFillStyle('#fff')
      ctx.beginPath()
      ctx.arc(70.800.40.0.2 * Math.PI)
      ctx.clip()
      ctx.drawImage(res[1].path, 30.760.80.80)
      ctx.restore()

      ctx.font = 'normal bold 26px sans-serif';
      ctx.fillText("On The fifth".130.770)
      ctx.font = 'normal normal 20px sans-serif';
      ctx.setFillStyle('# 616165')
      ctx.fillText("It has been changed.".130.810)
      let wordWidth = ctx.measureText("It has been changed.").width
      ctx.setFillStyle('# 333')
      ctx.fillText("4".130 + wordWidth + 5.810)
      let numWidth = ctx.measureText("4").width
      ctx.setFillStyle('# 616165')
      ctx.fillText("Day".130 + wordWidth + 5 + numWidth + 5.810)

      // Draw the qr code
      ctx.drawImage(res[2].path, 410.750.100.100)
      ctx.drawImage(res[3].path, 30.860.20.20)
      ctx.setFillStyle('#AAABAD')
      ctx.fillText("Long press to view".58.860)

      ctx.draw(false.() = > {
        this.canvasToImage()
      })
    })
  },
  canvasToImage() {
    wx.canvasToTempFilePath({
      canvasId: 'shareCanvas'.success: res= > {
        wx.hideLoading()
        this.setData({
          imgSrc: res.tempFilePath
        })
      }
    })
  },
  download() {
    wx.showLoading({
      title: 'Saving'
    });
    wx.saveImageToPhotosAlbum({
      filePath: this.data.imgSrc,
      success: function () {
        wx.showToast({
          title: 'Saved successfully'
        });
      },
      fail: function (e) {
        wx.showToast({
          title: 'Save failed'
        });
      },
      complete: function () {
        wx.hideLoading()
      }
    })
  }
})
Copy the code