Originally published in blog: blog.zhangbing.site
1, the origin of
A calendar book “Great Programmer 2021”, a lot of friends circle sun, I also bought, very thick, paper book, now has rarely read, plus this is a calendar book, I hope is open every day to see. What happens is that you either forget to read today’s content, or you read it for a few days and then stop reading it for the rest of the day.
Great Programmer 2021 has since been open-source on Github.
As a result! I just want to make a small program, because the frequency of mobile phone is too high every day, fragmentation time is also a lot of, plus the small program does not need to install the advantages of disposable, easy to use, there is no sense of pressure.
In addition to their own has not a serious small program works, now very hot cloud development also did not use, especially small program cloud development, he he in the end use up cool? (Great!)
As a result! Open dry!
2. Product design
This is the most nerve-racking part, what is the little program going to do, draw a prototype? As a “senior” programmer, I’ve never actually drawn prototypes and designs. Confused, what tools should I use? Although I knew there was a great tool called Sketch, there are many online design tools, such as knife sharpening, but I never used it. Finally, I bravely used knife sharpening to draw a prototype, a very simple prototype, which is the level of wireframing.
Here is the prototype I drew….
Knife sharpening link: modao.cc
It was a process of constantly coming up with new ideas, so it was a process of going back and forth, and product design took a couple of days, learning how to prototype and implement all the ideas that were floating around in my head.
In this process, I continue to add their own needs, once added what in history today, zhihu calendar and so on all kinds of content, and finally was killed by their own cruel one by one, leaving only pure programming calendar content.
How applets are better than Great Programmer 2021:
- The great Programmer 2021 is a game with real graphics, which is a little bit less of a taste…
- Small programs support collection, sharing, which is innate paper books do not have
- Based on Great Programmer 2021 but not exactly the same, I made a few minor changes or additions.
The final product can be pulled down in section 4 below to see the screenshot of the interface.
🤝 in view of not being good at product and design, we sincerely invite UI and product partners to form a team together and have the opportunity to make some products together, so that our ideas can be grounded and take root.
3, development,
The time ratio between the product design phase and the development phase is about 8:2. With prototyping, development is very fast. After all, there is nothing complicated.
The following focus on the implementation of the sharing poster function, other functions are very “ordinary”, if you have what other want to know the point can leave a message.
3.1. Select the poster sharing scheme
Before developing the function of sharing posters, I also looked at the general scheme on the Internet. Finally, I chose the extension component of wechat applet: WXML-to-Canvas. In the applet, static templates and styles are used to draw canvas and export pictures, which can be used to generate shared pictures and other scenes.
Why I didn’t use another plan:
- Writing canvas is too troublesome
- Back-end generation front-end access, too troublesome, I this small procedure is very simple and unnecessary
- Open source small program poster component, tried a, the feeling is not very easy to use, some no documentation to use up
Above, a mule is pulled out for a walk, and the poster below is dynamically drawn using wXML-to-canvas.
3.2. Introduce the WXML-to-Canvas component
Wxml-to-canvas has a lot of limitations, and it is difficult to use if I am inexperienced at the first time. If I have to do it again, I will be much faster.
The official example simply teaches you how to generate posters, lacks context and how to integrate it into your project and logic, requires a bit of thinking.
Step1.npm installation, refer to small program NPM support
npm install --save wxml-to-canvas
Copy the code
Step2. JSON component declaration
{
"usingComponents": {
"wxml-to-canvas": "wxml-to-canvas"}}Copy the code
Step3. WXML introduces components
<view class="share-image-container">
<wxml-to-canvas
id="canvas"
width="{{canvasWidth}}"
height="{{canvasHeight}}"
></wxml-to-canvas>
</view>
Copy the code
3.3 poster sharing logic
Click the poster share button at the bottom of the programming calendar applet to generate a Canvas preview on the current page, and then regenerate it into a picture to jump to the poster picture preview and Save page.
The.share-image-container class above looks like this:
.share-image-container {
border: 1px solid red;
position: absolute;
transform: translateY(-1000%);
bottom: 0;
z-index: 0;
}
Copy the code
This is where the wXML-to-Canvas component is debuggable. If this class is removed, it looks like this:
3.4. Obtaining the instance of JS
Step4.js to obtain the instance
import RenderCodeToWXML from "./renderCodeWXML.js";
Page({
data: {
canvasWidth: 373.canvasHeight: 720.bannerImgHeight: 240.bannerImgWdith: 320,},renderToCanvas() {
wx.showLoading({
title: "Under treatment..."});this.canvas = this.selectComponent("#canvas");
const {
canvasWidth,
canvasHeight,
bannerImgWdith,
bannerImgHeight,
} = this.data;
let renderToWXML = new RenderCodeToWXML(
canvasWidth,
canvasHeight,
bannerImgWdith,
bannerImgHeight
);
const wxml = renderToWXML.renderWXML();
const style = renderToWXML.renderStyle();
const p1 = this.canvas.renderToCanvas({ wxml, style });
p1.then((res) = > {
// console.log('container', res.layoutBox)
app.globalData.container = res;
this.container = res;
this.extraImage();
}).catch((err) = > {
wx.hideLoading();
console.log("err", err);
});
},
extraImage() {
const p2 = this.canvas.canvasToTempFilePath();
p2.then((res) = > {
wx.hideLoading();
// app.globalData.share = res
wx.navigateTo({
url: ".. /shareImage/shareImage".success: function(res2) {
// Send data to the opened page via eventChannel
res2.eventChannel.emit(
"acceptDataFromOpenerPage",
{
share: res,
container: app.globalData.container,
tab: app.globalData.tab,
date: app.globalData.dateInfo.strings, } ); }}); }).catch((err) = > {
wx.hideLoading();
wx.showToast({
title: err,
icon: "none"}); }); }});Copy the code
RenderToCanvas gets the WXML and Style from renderCodeWxml. js and calls renderToCanvas to render:
const wxml = renderToWXML.renderWXML();
const style = renderToWXML.renderStyle();
const p1 = this.canvas.renderToCanvas({ wxml, style });
Copy the code
Finally, call this.extraimage () in p1.then; Method jumps to the next page and passes the parameters through eventChannel.emit.
Let’s see what’s in RenderCodeWxml.js:
const app = getApp();
export default class RenderDataToWXML {
constructor(canvasWidth, canvasHeight, imgWidth, imgHeight) {
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.imgWidth = imgWidth;
this.imgHeight = imgHeight;
}
renderWXML() {
const { dateInfo, data, userInfo } = app.globalData;
const openId = wx.getStorageSync("openId");
let pData = "";
let pMore = "";
let banner = "";
if (data.data.event) {
pData = data.data.event.join("");
}
if (data.data.coding) {
pData = data.data.coding.join("");
}
if (data.data.landmark) {
pData = data.data.landmark.join("");
}
if (data.data.more) {
pMore = data.data.more[0];
} else if (data.data.people) {
pMore = data.data.people[0].split(":").join(",");
} else {
pMore = "";
}
if (data.data.img) {
banner = `
<view class="banner">
<image class="banner-image" mode="aspectFit" src="${data.data.img.url}" />
</view>`;
}
if (pData.length >= 156) {
pData = pData.substring(0.152) + "...";
}
if (pMore.length >= 50) {
pMore = pMore.substring(0.48) + "...";
}
let avatar = "";
if (userInfo && userInfo.avatarUrl) {
avatar = `<view class="avatar">
<image class="avatar-image" src="${userInfo.avatarUrl}" />
<text class="avatar-nikename">${userInfo.nickName}Invite you to use </text> </view> ';
}
let wxmlMore = pMore;
if (wxmlMore) {
wxmlMore = `
<view>
<text class="p-more">${pMore}</text>
</view>
`;
}
const wxml = `
<view class="container">
<view class="top">
<view class="top-left">
<view><text class="en">${dateInfo.date.monthEN}</text></view>
<view><text class="cn">${dateInfo.lunarDate}</text></view>
</view>
<view><text class="top-center">${dateInfo.date.day}</text></view>
<view class="top-right">
<view><text class="en">${dateInfo.date.weekEN}</text></view>
<view><text class="cn">${dateInfo.date.weekCN}</text></view>
</view>
</view>
${banner}
<view class="middle">
<view>
<text class="p-data">${pData}</text>
</view>
${wxmlMore}
</view>
<view class="qrcode">
<view class="appinfo">
${avatar}<view><text class="appname"> </text></view> <view><text class="appdesc"> </text></view> </view> <view class="qrcode-image"> <image class="image" mode="aspectFit" src="https://7072-programming-calendar-3b8b7a7d082-1304448256.tcb.qcloud.la/qr/${openId}-qr.png? sign=b5a610dc6ae15c9427720ab617a2f18a&t=1609438339" /> </view> </view> </view> `;
return wxml;
}
/ / canvas style
renderStyle() {
const contentWidth = this.canvasWidth - 50;
const mainColor = "#1296db";
const style = {
container: {
width: this.canvasWidth,
height: this.canvasHeight,
backgroundColor: "#fff",},top: {
width: this.canvasWidth,
height: 82.backgroundColor: mainColor,
flexDirection: "row".justifyContent: "space-around".alignItems: "center",},topLeft: {
width: this.canvasWidth / 3.height: 82.textAlign: "center".alignItems: "center",},topCenter: {
width: this.canvasWidth / 3.height: 82.lineHeight: 82.fontSize: 72.textAlign: "center".color: "#ffffff",},topRight: {
width: this.canvasWidth / 3.height: 82,},en: {
width: this.canvasWidth / 3.height: 30.fontSize: 20.textAlign: "center".color: "#ffffff".marginTop: 15,},cn: {
width: this.canvasWidth / 3.height: 30.textAlign: "center".color: "#ffffff",},banner: {
width: this.canvasWidth,
flexDirection: "row".justifyContent: "center".marginTop: 20,},bannerImage: {
width: this.imgWidth,
height: this.imgHeight,
},
middle: {
flexDirection: "column".justifyContent: "center".alignItems: "center".marginTop: 20,},pData: {
width: contentWidth,
height: 170.lineHeight: "1.8 em",},pMore: {
width: contentWidth,
height: 60.lineHeight: "1.8 em",},qrcode: {
height: 130.flexDirection: "row".justifyContent: "space-between".backgroundColor: "#CCE6FF".paddingLeft: 20.paddingTop: 20,},qrcodeImage: {
width: 90.height: 90.marginRight: 20.borderRadius: 45.flexDirection: "row".justifyContent: "center".alignItems: "center".backgroundColor: "#fff",},image: {
width: 90.height: 90.scale: 0.9.borderRadius: 45,},appinfo: {
flexDirection: "column".justifyContent: "flex-start".alignItems: "flex-start".height: 80,},avatar: {
flexDirection: "row".justifyContent: "flex-start".width: this.canvasWidth / 1.8.height: 30,},avatarImage: {
width: 30.height: 30.borderRadius: 15.marginRight: 5,},avatarNikename: {
width: this.canvasWidth / 1.8.height: 22.lineHeight: 22.marginTop: 5,},appname: {
width: this.canvasWidth / 2.height: 23.fontSize: 16.color: "#0081FF".marginTop: 8.marginLeft: 35,},appdesc: {
width: this.canvasWidth / 2.height: 20.fontSize: 14.marginLeft: 35,}};return style;
}
// omit irrelevant code
}
Copy the code
This file is where we draw the poster, which is to generate the WXML and Style and export it.
3.5. Considerations for the WXML-to-Canvas component
The WXML-to-Canvas component has limited support for WXML templates:
- support
<view>
,<text>
,<image>
Three tags, passclass
matchingstyle
The style in the object. - Words must be used
<text>
Tag contains, otherwise not displayed. The width and height must be set. The width of the text must be determined first, and will be truncated automatically if it exceeds it. So dynamic text can be set according to the number of words, dynamic width.
Style:
- The value of the object attribute is the CASS hump of the corresponding WXML tag. Specify width and height attributes for each element, otherwise layout errors will result.
- When there are multiple classnames, the next one takes precedence, and the child element inherits the inheritable attributes of the parent element.
- The elements are all flex layout. Left/Top are valid only for absolute positioning.
Because the text must be included with the
tag, and the width and height must be set, the text width must be determined first and truncated automatically if it exceeds it. So dynamic text can be set according to the number of words, dynamic width. So writing a layout is a hassle, and I recommend setting a background for each element so that you can see the range and width of the element rendering. As follows:
BorderColor marginBottom/marginTop can use, although WeChat didn’t write in the document.
3.6 Poster preview and download page
After the canvas is generated and the interface is called to generate the image, we jump to the next page with parameters. Let’s take a look at WXML first, very simple:
<view>
<view class="share-container">
<image
src="{{src}}"
mode="widthFix"
class="image"
style="height: {{height}}px;"
></image>
</view>
<view class="save-button">
<van-button
bind:tap="saveImage"
block
round
icon="down"
size="large"
type="info"
>Save to mobile </van-button ></view>
</view>
Copy the code
Js logic
const app = getApp();
Page({
data: {
src: "".date: "".width: "".height: "",},onLoad() {
const eventChannel = this.getOpenerEventChannel();
eventChannel.on("acceptDataFromOpenerPage".(data) = > {
// console.log("data", data)
this.setData({
showPopup: true.date: data.date,
src: data.share.tempFilePath,
width: data.container.layoutBox.width,
height: data.container.layoutBox.height,
});
});
},
getDatestr() {
const { strings } = app.globalData.dateInfo;
return strings;
},
saveImage() {
wx.showLoading({
title: "Under treatment..."});const _this = this;
wx.getSetting({
success(res) {
if(! res.authSetting["scope.writePhotosAlbum"]) {
wx.authorize({
scope: "scope.writePhotosAlbum".success() {
_this.save();
},
fail() {
wx.showToast({
title: "Authorization failed".icon: "none"}); }}); }else{ _this.save(); }}}); },save() {
wx.saveImageToPhotosAlbum({
filePath: this.data.src,
success() {
wx.showToast({
title: "Saved successfully".icon: "none"}); },fail() {
wx.showToast({
title: "Save failed".icon: "none"}); }}); }});Copy the code
The above is my logic and code for poster function development, just for reference, if you have relevant experience welcome to discuss and exchange, leave your insights.
4. Screenshot of programming calendar applet page
Finally, a few small program page screenshots
preview | preview |
---|---|
5. Plan for subsequent iterations
Increased user level plan, corresponding level can be exchanged for some gifts, the rest… What do you have in mind? Welcome to exchange!