Abstract: Small program development necessary skills ah…
- How does a small program generate posters to share moments
- Author: Xiao Bai
FundebugReproduced with authorization, copyright belongs to the original author.
It has been some time since the project requirements were written, but I still want to come back and summarize. One is to review and optimize the project, and the other is to make a record of the pits to avoid similar problems in the future.
demand
Make use of wechat’s powerful social ability to achieve the purpose of fission through small programs to attract new users.
The resulting poster is as follows:
Demand analysis
1. Use the API provided by the official mini program to directly share and forward to the wechat group to open the mini program. 2
Implementation scheme
A, how to achieve analysis
I believe everyone will have similar confusion, that is, how to draw a poster as designed by the product. Actually, I did not know how to draw a picture on canvas at that time, but I seriously thought about it, so that users can save the picture to the album and share it in the moments of friends. But to draw the picture above not only have text and numbers, pictures, two-dimensional code and so on and are alive, how to dynamically generate this. After careful consideration, text, numbers and background images need to be drawn on the canvas bit by bit, so that a picture is finally synthesized through API and exported to the mobile phone album.
2. Problems to be solved
- Dynamic acquisition and drawing of TWO-DIMENSIONAL code (including how to generate small program two-dimensional code, public two-dimensional code, open webpage two-dimensional code)
- How to draw the background image and obtain the picture information
- Save the finished picture to a local album
- Handle whether the user cancels authorization and saves it to the album
Three, implementation steps
Here I specifically write around the questions raised above, describe the approximate implementation process
(1) First create canvas canvas. I set the canvas position to negative to prevent it from being displayed on the page, because I tried to dynamically show and hide the canvas by judging conditions. There would be problems in drawing, so I adopted this method, and the size of the canvas must be set.
<canvas canvas-id="myCanvas" style="width: 690px; height:1085px; position: fixed; top: -10000px;"></canvas>
Copy the code
After the canvas is created, draw the background image first. Since the background image is stored locally, obtain the canvas-id attribute of the < Canvas > component and create the Canvas drawing context CanvasContext object through createCanvasContext. Draw an image to the canvas using drawImage. The first parameter is the local address of the image, the next two parameters are the x and Y axes of the image relative to the upper left corner of the canvas, and the last two parameters set the width and height of the image.
const ctx = wx.createCanvasContext('myCanvas')
ctx.drawImage('/img/study/shareimg.png'.0.0.690.1085)
Copy the code
③ After creating the background image, draw the head picture, words and numbers on the background image. You can use getImageInfo to obtain profile picture information. To obtain network images, configure the Download domain name first. For details, configure the download domain name in the background Settings of the mini program.
To obtain the address of the head, first measure the size of the head in the canvas and the coordinates of the X-axis and Y-axis, where result[0] is an image address that I return as a promise package
let headImg = new Promise(function (resolve) {
wx.getImageInfo({
src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`.success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: 'Network error please try again'.icon: 'loading'})}})})let avatarurl_width = 60.// The width of the drawing head
avatarurl_heigth = 60.// Draw the height of the avatar
avatarurl_x = 28.// The position of the drawing head on the canvas
avatarurl_y = 36; // The position of the drawing head on the canvas
ctx.save(); // Save the state so that it can be used after drawing the circle
ctx.beginPath(); // Start drawing
The first two parameters determine the coordinates of the center (x,y). The third parameter specifies the radius of the circle. The fourth parameter specifies the drawing direction
ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2.0.Math.PI * 2.false);
ctx.clip(); // Draw the circle and then cut the original canvas to any shape and size. Once an area is clipped, all subsequent drawings are confined to the clipped area
ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // Move the image forward
Copy the code
For example here of how to draw text, I want to draw the following this “word”, for example, requires dynamic access to count the total width of the front, so you can set the x coordinate of “words”, here I am want to measureText to measure the width of the font, but on the iOS side first to obtain the width of the value is wrong, for this problem, I also mentioned a bug in the wechat developer community, so I want to use another method to achieve, is to obtain the width of a word under normal circumstances, and then multiply the total number of words to get the total width, try is ok.
let allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
ctx.font = 'normal normal 30px sans-serif';
ctx.setFillStyle('#ffffff')
ctx.fillText('word', allReading, 150);
Copy the code
④ Draw the two-dimensional code of the public number, and obtain the profile picture is the same, but also through the interface to return the picture network address, and then through getImageInfo to obtain the two-dimensional code of the public number picture information
⑤ How to draw small program code, specific official website documents also give the generation of infinitesimal program code interface, through the generated small program can open any small program page, and two-dimensional code is permanently valid, specific call which small program two-dimensional code interface has different application scenarios, specific can see what the official document says, That is to say, the front end calls the small program code returned by the back-end interface by passing parameters, and then draws it on the canvas (the same as drawing the profile picture and two-dimensional code of the public account written above).
ctx.drawImage('Local address of applets', X-axis, Y-axis, width, height)Copy the code
⑥ Finally draw the canvas into a picture and return the picture address
wx.canvasToTempFilePath({
canvasId: 'myCanvas'.success: function (res) {
canvasToTempFilePath = res.tempFilePath // The address of the returned image is stored in a global variable
that.setData({
showShareImg: true
})
wx.showToast({
title: 'Drawn successfully',}}),fail: function () {
wx.showToast({
title: 'Draw failed',}}),complete: function () {
wx.hideLoading()
wx.hideToast()
}
})
Copy the code
⑦ Save to system album; Determine whether the user has enabled the user authorization album and process the results in different cases. For example, if the user authorizes according to the normal logic, there is no problem. However, if some users click to cancel the authorization, how to deal with it? If not, there will be some problems. Therefore, when the user clicks to cancel authorization, a pop-up prompt will appear. When it clicks again, it will actively jump to Settings to guide the user to open authorization, so as to achieve the purpose of saving to the album and sharing the circle of friends.
// Gets whether the user has enabled the user authorization album
if(! openStatus) { wx.openSetting({success: (result) = > {
if (result) {
if (result.authSetting["scope.writePhotosAlbum"= = =true) {
openStatus = true;
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: 'The picture has been saved successfully, please share it in moments.'.icon: 'none'.duration: 2000
})
},
fail() {
wx.showToast({
title: 'Save failed'.icon: 'none'})}})}}},fail: (a)= >{},complete: (a)= >{}}); }else {
wx.getSetting({
success(res) {
// If not, obtain authorization
if(! res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: 'The picture has been saved successfully, please share it in moments.'.icon: 'none'.duration: 2000
})
},
fail() {
wx.showToast({
title: 'Save failed'.icon: 'none'
})
}
})
},
fail() {
// If the user refused or did not authorize, open the authorization window again
openStatus = false
console.log('Please allow access to album')
wx.showToast({
title: 'Please allow access to album'.icon: 'none'})}})}else {
// If yes, save it directly
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: 'The picture has been saved successfully, please share it in moments.'.icon: 'none'.duration: 2000
})
},
fail() {
wx.showToast({
title: 'Save failed'.icon: 'none'
})
}
})
}
},
fail(err) {
console.log(err)
}
})
}
Copy the code
conclusion
So far, all the steps have been realized. When drawing, I will encounter some asynchronous requests for data returned by the background, so I encapsulate it with promise and async and await to ensure that the exported image information is complete. Where you do encounter some potholes during the drawing process. Examples include images that were initially exported at the wrong scale, text widths measured incorrectly with measureText, and text colors that were sometimes drawn multiple times (possibly due to network problems). If you also encounter some pits can be discussed together to make a record, attached below the complete code
import regeneratorRuntime from '.. /.. /utils/runtime.js' // Import modules
const app = getApp(),
api = require('.. /.. /service/http.js');
var ctx = null.// Create a canvas object
canvasToTempFilePath = null.// Save the final generated exported image address
openStatus = true; // Declare a global variable to determine whether it is authorized to save to the album
// Obtain the qr code of wechat official account
getCode: function () {
return new Promise(function (resolve, reject) {
api.fetch('/wechat/open/getQRCodeNormal'.'GET').then(res= > {
console.log(res, 'Obtain the QR code of wechat official account')
if (res.code == 200) {
console.log(res.content, 'codeUrl')
resolve(res.content)
}
}).catch(err= > {
console.log(err)
})
})
},
// Generate the poster
async createCanvasImage() {
let that = this;
// Click generate poster data buried point
that.setData({
generateId: 'Click to generate poster'
})
if(! ctx) {let codeUrl = await that.getCode()
wx.showLoading({
title: 'Drawing... '
})
let code = new Promise(function (resolve) {
wx.getImageInfo({
src: codeUrl,
success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: 'Network error please try again'.icon: 'loading'})}})})let headImg = new Promise(function (resolve) {
wx.getImageInfo({
src: `${app.globalData.baseUrl2}${that.data.currentChildren.headImg}`.success: function (res) {
resolve(res.path)
},
fail: function (err) {
console.log(err)
wx.showToast({
title: 'Network error please try again'.icon: 'loading'})}})})Promise.all([headImg, code]).then(function (result) {
const ctx = wx.createCanvasContext('myCanvas')
console.log(ctx, app.globalData.ratio, 'ctx')
let canvasWidthPx = 690 * app.globalData.ratio,
canvasHeightPx = 1085 * app.globalData.ratio,
avatarurl_width = 60.// The width of the drawing head
avatarurl_heigth = 60.// Draw the height of the avatar
avatarurl_x = 28.// The position of the drawing head on the canvas
avatarurl_y = 36.// The position of the drawing head on the canvas
codeurl_width = 80.// Draw the width of the QR code
codeurl_heigth = 80.// Draw the height of the QR code
codeurl_x = 588.// The position of the qr code drawn on the canvas
codeurl_y = 984.// The position of the qr code drawn on the canvas
wordNumber = that.data.wordNumber, // Get the total number of words read
// nameWidth = ctx.measureText(that.data.wordNumber).width, // Get the width of the total number of words read
// allReading = ((nameWidth + 375) - 325) * 2 + 380;
// allReading = nameWidth / app.globalData.ratio + 325;
allReading = 97 / 6 / app.globalData.ratio * wordNumber.toString().length + 325;
console.log(wordNumber, wordNumber.toString().length, allReading, 'Get the width of total read words')
ctx.drawImage('/img/study/shareimg.png'.0.0.690.1085)
ctx.save(); // Save the state so that it can be used after drawing the circle
ctx.beginPath(); // Start drawing
The first two parameters determine the coordinates of the center (x,y). The third parameter specifies the radius of the circle. The fourth parameter specifies the drawing direction
ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2.0.Math.PI * 2.false);
ctx.clip(); // Draw the circle and then cut the original canvas to any shape and size. Once an area is clipped, all subsequent drawings are confined to the clipped area
ctx.drawImage(result[0], avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // Move the image forward
ctx.restore(); // Restore the previously saved drawing context state to continue drawing
ctx.setFillStyle('#ffffff'); // Text color
ctx.setFontSize(28); // Text size
ctx.fillText(that.data.currentChildren.name, 103.78); // Draw text
ctx.font = 'normal bold 44px sans-serif';
ctx.setFillStyle('#ffffff'); // Text color
ctx.fillText(wordNumber, 325.153); // Draw text
ctx.font = 'normal normal 30px sans-serif';
ctx.setFillStyle('#ffffff')
ctx.fillText('word', allReading, 150);
ctx.font = 'normal normal 24px sans-serif';
ctx.setFillStyle('#ffffff'); // Text color
ctx.fillText('Beat the nation'.26.190); // Draw text
ctx.font = 'normal normal 24px sans-serif';
ctx.setFillStyle('#faed15'); // Text color
ctx.fillText(that.data.percent, 154.190); // Draw the percentage of children
ctx.font = 'normal normal 24px sans-serif';
ctx.setFillStyle('#ffffff'); // Text color
ctx.fillText('The little friend'.205.190); // Draw the percentage of children
ctx.font = 'normal bold 32px sans-serif';
ctx.setFillStyle('# 333333'); // Text color
ctx.fillText(that.data.singIn, 50.290); // Number of check-in days
ctx.fillText(that.data.reading, 280.290); // Reading time
ctx.fillText(that.data.reading, 508.290); // How long do you listen to the book
// Book reading structure
ctx.font = 'normal normal 28px sans-serif';
ctx.setFillStyle('#ffffff'); // Text color
ctx.fillText(that.data.bookInfo[0].count, 260.510);
ctx.fillText(that.data.bookInfo[1].count, 420.532);
ctx.fillText(that.data.bookInfo[2].count, 520.594);
ctx.fillText(that.data.bookInfo[3].count, 515.710);
ctx.fillText(that.data.bookInfo[4].count, 492.828);
ctx.fillText(that.data.bookInfo[5].count, 348.858);
ctx.fillText(that.data.bookInfo[6].count, 212.828);
ctx.fillText(that.data.bookInfo[7].count, 148.726);
ctx.fillText(that.data.bookInfo[8].count, 158.600);
ctx.font = 'normal normal 18px sans-serif';
ctx.setFillStyle('#ffffff'); // Text color
ctx.fillText(that.data.bookInfo[0].name, 232.530);
ctx.fillText(that.data.bookInfo[1].name, 394.552);
ctx.fillText(that.data.bookInfo[2].name, 496.614);
ctx.fillText(that.data.bookInfo[3].name, 490.730);
ctx.fillText(that.data.bookInfo[4].name, 466.850);
ctx.fillText(that.data.bookInfo[5].name, 323.878);
ctx.fillText(that.data.bookInfo[6].name, 184.850);
ctx.fillText(that.data.bookInfo[7].name, 117.746);
ctx.fillText(that.data.bookInfo[8].name, 130.621);
ctx.drawImage(result[1], codeurl_x, codeurl_y, codeurl_width, codeurl_heigth); // Draw the avatar
ctx.draw(false.function () {
// canvas Converts the canvas to an image and returns the image address
wx.canvasToTempFilePath({
canvasId: 'myCanvas'.success: function (res) {
canvasToTempFilePath = res.tempFilePath
that.setData({
showShareImg: true
})
console.log(res.tempFilePath, 'canvasToTempFilePath')
wx.showToast({
title: 'Drawn successfully',}}),fail: function () {
wx.showToast({
title: 'Draw failed',}}),complete: function () {
wx.hideLoading()
wx.hideToast()
}
})
})
})
}
},
// Save to system album
saveShareImg: function () {
let that = this;
// Data buried point click save learning love poster
that.setData({
saveId: 'Save the Love Poster'
})
// Gets whether the user has enabled the user authorization album
if(! openStatus) { wx.openSetting({success: (result) = > {
if (result) {
if (result.authSetting["scope.writePhotosAlbum"= = =true) {
openStatus = true;
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: 'The picture has been saved successfully, please share it in moments.'.icon: 'none'.duration: 2000
})
},
fail() {
wx.showToast({
title: 'Save failed'.icon: 'none'})}})}}},fail: (a)= >{},complete: (a)= >{}}); }else {
wx.getSetting({
success(res) {
// If not, obtain authorization
if(! res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: 'The picture has been saved successfully, please share it in moments.'.icon: 'none'.duration: 2000
})
},
fail() {
wx.showToast({
title: 'Save failed'.icon: 'none'
})
}
})
},
fail() {
// If the user refused or did not authorize, open the authorization window again
openStatus = false
console.log('Please allow access to album')
wx.showToast({
title: 'Please allow access to album'.icon: 'none'})}})}else {
// If yes, save it directly
openStatus = true
wx.saveImageToPhotosAlbum({
filePath: canvasToTempFilePath,
success() {
that.setData({
showShareImg: false
})
wx.showToast({
title: 'The picture has been saved successfully, please share it in moments.'.icon: 'none'.duration: 2000
})
},
fail() {
wx.showToast({
title: 'Save failed'.icon: 'none'
})
}
})
}
},
fail(err) {
console.log(err)
}
})
}
},
Copy the code