1、起源

朋友圈晒的很多的一本日历书《了不起的程序员 2021》,我也买了,很厚,纸质书嘛,现在已经很少看了,加上这是一本日历书,希望是每天都打开看。可实际上的情况是,要么忘记看今天的内容,要么一口气看了好几天的内容,然后剩下几天又不看了。

后来《了不起的程序员 2021》在 Github 开源了。

于是乎!我就想做一个小程序,因为手机每天打开的频率太高了,碎片时间也很多,加上小程序的不用安装用完即走的优点,使用方便,不会有压力感。

再加上自己还没有一款正儿八经的小程序作品,对现在很火的云开发也没怎么用过,特别是小程序云开发,他他到底用起来爽不爽呢?(很爽!)

于是乎!开干!

2、产品设计

这是最伤脑筋的部分,小程序到底要做成什么样,画个原型图?作为一个『资深』程序员,从来没正经画过原型和设计。手足无措,改用什么工具?虽然我知道有 Sketch 这个神器,还很多在线设计工具,比如磨刀,但从来没用过啊,最后硬着头皮用磨刀画了画原型,很简陋的原型,就是线框图级别。

如下是我画的原型…

磨刀链接:https://modao.cc

这个过程不断有新的想法,所以改来该去,产品设计花了好几天,学习怎么画原型,实现脑子里乱七八糟的各种想法。

在这个过程中我不断的给自己家需求,一度增加了什么历史上的今天、知乎日历等等各种内容,最后还是被自己狠心一一毙掉了,只留下纯粹的编程日历内容。

小程序比《了不起的程序员 2021》更好的方面:

  1. 真实配图,《了不起的程序员 2021》的配图都是手绘图,看起来少了点那种味儿。。。
  2. 小程序支持收藏、分享,这是纸质书先天不具备的
  3. 基于《了不起的程序员 2021》但不是完全一样,我做了一些小小的修改或增加一些内容。

最终成品可以拉倒下面第五节看看界面截图。

鉴于对产品和设计不擅长,在此诚邀 UI、产品小伙伴,一起租一个团队,有机会一起做一些产品,让我们的想法能落地,生根发芽。

3、开发

产品设计阶段和开发阶段占用的时间比大概是 8:2 左右,有了原型开发很快,毕竟也没什么复杂的东西。

下面重点说一下分享海报功能的实现吧。

3.1、选择海报分享方案

在开发分享海报功能之前我也看了下网上大致的方案,最后我选择了微信小程序自己的扩展组件:wxml-to-canvas,小程序内通过静态模板和样式绘制 canvas ,导出图片,可用于生成分享图等场景。

我为什么不用其他方案:

  • 手写 canvas,太麻烦
  • 后端生成前端获取,太麻烦,我这个小程序很简单没必要
  • 开源小程序海报组件,尝试过一个,感觉也不太好用,有些没文档用起来吃力

上图,是骡子是马拉出来遛遛,下图的的海报就是通过 wxml-to-canvas 动态绘制的。

3.2、引入 wxml-to-canvas 组件

wxml-to-canvas 的限制很多,第一次没经验的话觉得很难用,如果再让我做一次,我就快很多了。

官方的示例只单纯教你怎么生成海报,缺乏上下文和怎么整合进你的项目及逻辑,需要费一下脑子。

Step1. npm 安装,参考 小程序 npm 支持

npm install --save wxml-to-canvas

Step2. JSON 组件声明

{"usingComponents": {"wxml-to-canvas": "wxml-to-canvas"}
}

Step3. wxml 引入组件

<view class="share-image-container"><wxml-to-canvasid="canvas"width="{{canvasWidth}}"height="{{canvasHeight}}"></wxml-to-canvas>
</view>

3.3、海报分享逻辑说明

点击编程日历小程序底部的海报分享按钮,在当前页面生成 canvas 预览图,然后再生成图片跳转到海报图片预览和保存页面。

上面的 .share-image-container 类如下:

.share-image-container {border: 1px solid red;position: absolute;transform: translateY(-1000%);bottom: 0;z-index: 0;
}

即在页面外生成 canvas,也是在这里调试 wxml-to-canvas 组件效果的地方,去掉该类的样子如下:

3.4、js 获取实例

Step4. js 获取实例

import RenderCodeToWXML from "./renderCodeWXML.js";Page({data: {canvasWidth: 373,canvasHeight: 720,bannerImgHeight: 240,bannerImgWdith: 320,},renderToCanvas() {wx.showLoading({title: "处理中...",});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 = reswx.navigateTo({url: "../shareImage/shareImage",success: function(res2) {// 通过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",});});},
});

这里主要就是从 renderCodeWXML.js 中获取 WXML 和 Style,然后调用 canvas 的 renderToCanvas 方法进行渲染:

const wxml = renderToWXML.renderWXML();
const style = renderToWXML.renderStyle();
const p1 = this.canvas.renderToCanvas({ wxml, style });

最后在 p1.then 里调用 this.extraImage(); 方法跳转到下一个页面,并通过 eventChannel.emit 方式传递参数。

来看看 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}邀请你使用</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样式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.8em",},pMore: {width: contentWidth,height: 60,lineHeight: "1.8em",},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;}// 省略不相关代码
}

该文件就是我们画海报的地方,就是生成 WXML 和 Style 然后导出

3.5、wxml-to-canvas 组件的注意事项

wxml-to-canvas 组件对 wxml 模板支持有限 :

  • 支持 <view><text><image> 三种标签,通过 class 匹配 style 对象中的样式。
  • 文字必须用 <text> 标签包含,否则不显示。并且必须设置宽高。文字宽度必须先确定,超出则会自动截断。所以动态文字可以根据字数,动态设置宽度。

样式方面:

  • 对象属性值为对应 wxml 标签的 cass 驼峰形式需为每个元素指定 width 和 height 属性,否则会导致布局错误
  • 存在多个 className 时,位置靠后的优先级更高,子元素会继承父级元素的可继承属性。
  • 元素均为 flex 布局。left/top 等 仅在 absolute 定位下生效。

因为文字必须用 <text> 标签包含,并且必须设置宽高,文字宽度必须先确定,超出则会自动截断。所以动态文字可以根据字数,动态设置宽度。所以写布局非常麻烦,我推荐大家为每一个元素设置背景,这样可以看到元素渲染的范围和宽高。如下所示:

borderColor/marginBottom/marginTop 可使用,虽然微信文档中没写。

3.6、海报预览和下载页面

生成 canvas 并调用接口生成图片后,我们携带参数跳转到下一个页面,先来看看 WXML,非常简单:

<view><view class="share-container"><imagesrc="{{src}}"mode="widthFix"class="image"style="height: {{height}}px;"></image></view><view class="save-button"><van-buttonbind:tap="saveImage"blockroundicon="down"size="large"type="info">保存到手机</van-button></view>
</view>

js 逻辑

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: "处理中...",});const _this = this;wx.getSetting({success(res) {if (!res.authSetting["scope.writePhotosAlbum"]) {wx.authorize({scope: "scope.writePhotosAlbum",success() {_this.save();},fail() {wx.showToast({title: "授权失败",icon: "none",});},});} else {_this.save();}},});},save() {wx.saveImageToPhotosAlbum({filePath: this.data.src,success() {wx.showToast({title: "保存成功",icon: "none",});},fail() {wx.showToast({title: "保存失败",icon: "none",});},});},
});

以上就是我开发海报功能的逻辑和代码,仅供参考吧,如果你有相关经验欢迎讨论交流,留下你的真知灼见吧。

4、编程日历小程序页面截图

最后,分几张小程序的页面截图

预览 预览

5、后续迭代计划

增加用户等级计划、对应等级可以换一些礼物,其余的。。。你有什么想法?欢迎交流!

编程日历小程序,对小程序云开发和生成分享海报的实践相关推荐

  1. 【小程序源码】云开发表情包制作神器微信小程序源码下载,支持各种自定义

    该款小程序是一个表情包制作 内容毕竟丰富,另外自定义制作方面也是特别的自由 支持自主上传图片,自定义文章,另外拥有多种素材模板以供选择 这是一款云开发的小程序,但是安装还是挺简单的 搭建教程: 首先使 ...

  2. 小程序源码:云开发表情包制作神器-多玩法安装简单

    该款小程序是一个表情包制作 内容毕竟丰富,另外自定义制作方面也是特别的自由 支持自主上传图片,自定义文章,另外拥有多种素材模板以供选择 这是一款云开发的小程序,但是安装还是挺简单的 搭建教程: 首先使 ...

  3. 微信小程序图片无法存入云开发数据库,求解决

    微信小程序图片无法存入云开发数据库,求解决 上传图片的js代码片段 上传图片的js代码片段 上传图片的js代码片段,觉得自己代码的没错,也能打印出来上传的图片地址,但是发现并没有存进数据库,求大神指点 ...

  4. 还不错的星座运势小程序源码下载+云开发无需服务器

    0 正文: 有兴趣的自行去研究吧,前端UI方面没得说,很好看的. 下方是介绍: 还不错的星座运势小程序源码下载+云开发无需服务器,一款星座运势源码,搭建非常简单,有一个小程序加上合法域名,然后替换流量 ...

  5. 微信小程序:全新独家云开发微群人脉

    今天给大家带来一款云开发版本的微群人脉小程序源码 该版本属于采集版本(群二维码自动采集) 该版本属于云开发版本(免服务器和域名) 这是一款不怕封小程序版本 PS:支持用户自主发布那一款还是有点危险因为 ...

  6. 小程序源码:云开发表情包制作神器微信小程序

    该款小程序是一个表情包制作 内容毕竟丰富,另外自定义制作方面也是特别的自由 支持自主上传图片,自定义文章,另外拥有多种素材模板以供选择 这是一款云开发的小程序,但是安装还是挺简单的 搭建教程: 首先使 ...

  7. 用于校园流浪猫信息记录和分享的小程序源码/微信云开发中大猫谱小程序源码

    用于校园流浪猫信息记录和分享的小程序源码/微信云开发中大猫谱小程序源码 ☑️ 编号:ym478 ☑️ 品牌:无 ☑️ 语言:微信小程序 ☑️ 大小:6.5MB ☑️ 类型:校园流浪猫信息记录和分享 ☑ ...

  8. 微信小程序开通腾讯云开发环境

    目录 微信小程序开通"腾讯云"开发环境 注册腾讯云账号 配置开发环境 微信小程序开通"腾讯云"开发环境 这里强调以下,标题是"腾讯云"开发环 ...

  9. 微信小程序 api+前端实现生成分享海报

    1.先看效果图,点击分享海报按钮,然后弹出分享海报 2.前端代码 这里用的组件有vant组件库还有canvas_drawer(一个画布组件) canvas_drawer下载地址https://gith ...

最新文章

  1. 解决load cycle count的一个偏方,告别硬盘卡卡响
  2. Spring Boot 密码加密的 2 种姿势!
  3. spring boot 整合web开发(二)
  4. 文件上传优化CommonsMultipartResolver
  5. OCJP试题分析第一章
  6. 计算发送延时与传播延迟_Kafka科普系列 | 轻松理解Kafka中的延时操作
  7. 简单理解梯度消失与梯度爆炸
  8. 关于AJAX的安全性
  9. dell笔记本外接显示器_小桌面 笔记本外接显示器的正确姿势
  10. cad看图软件看图软件哪个好?
  11. 解决Vue3的undefined问题
  12. 【工作流引擎--Snaker介绍】
  13. java 防止盗链AntiLeech
  14. [附源码]计算机毕业设计JAVA网上书店管理系统
  15. 读1973年的弹子球有感。_北国的雪_新浪博客
  16. 女生学计算机真的难吗,女生想学计算机科学与技术,怕很难学懂,这门学科是不是特别难?女生学到底好不好?...
  17. 六行python代码的爱心曲线
  18. 牛客每日练习----德玛西亚万岁,求最值,小牛再战
  19. 信用卡欺诈检测python
  20. 5款经典代码阅读器的使用方案对比

热门文章

  1. mysql中gender要用什么类型,如何选择合适的MySQL数据类型
  2. 银行项目企业网银转账对私转账功能的测试点
  3. 励志照亮人生 编程改变命运
  4. js后代选择器_后代选择器和子元素选择器的区别
  5. sqlplus报错ORA-12547: TNS:lost contact解决
  6. 诗歌(8)—梅花二首(其二)
  7. win7装xp双系统_UEFI新PC装Win7 32位系统:巧用”硬盘保护卡“解决
  8. [BJOI2019]勘破神机
  9. PHP绘制99的棋盘,JS canvas绘制五子棋的棋盘
  10. Python视频制作引擎Manim安装教程2021版(科学概念可视化)