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) :
- draw
540 * 900
On a white background; - Drawing on the top of
540 * 650
The head of the figure; - draw
I am attending XXX
These words; - Draw dividing lines;
- Draw avatar, nickname, more text days;
- Drawing two-dimensional code or small program code;
- Draw bottom
Long press to view
remind
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 * 900
On 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 * 650
The 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 XXX
These 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
,middle
andnormal
. 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 ittop
The 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
,center
andright
. 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 itleft
The 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. Useimage
Instead ofCanvas
Display to the page, you can avoidCanvas
Unit 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.Canvas
The 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