├── LICENSE
├── README.md
├── app.js
├── app.json
├── app.wxss
├── components
└── canvasdrawer
│ ├── canvasdrawer.js
│ ├── canvasdrawer.json
│ ├── canvasdrawer.wxml
│ └── canvasdrawer.wxss
├── images
├── avatar.jpeg
├── avatar_cover.jpeg
├── background.jpeg
├── pic.jpeg
└── wxacode.jpeg
├── pages
├── index
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
└── multiple
│ ├── multiple.js
│ ├── multiple.json
│ ├── multiple.wxml
│ └── multiple.wxss
└── project.config.json
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Di
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## canvas drawer
2 |
3 | 新增 [mpvue_canvas_drawer](https://github.com/kuckboy1994/mpvue_canvas_drawer)。之后同步更新。
4 |
5 | 新增由[simmzl](https://github.com/simmzl)开发移植的[wepy_canvas_drawer](https://github.com/simmzl/wepy_canvas_drawer)。之后同步更新。
6 |
7 | 做微信小程序中最好用的 `canvas` 绘图组件之一。
8 |
9 | 当前环境下,大家都非常需要分享到朋友圈这个功能,但是实现起来各有心酸(坑比较多),所以才有了如下的 `canvas` 绘图工具。
10 |
11 | 具有如下特性:
12 |
13 | - 简单易用 —— 一个 `json` 搞定绘制图片
14 | - 功能全 —— 满足 `90%` 的使用场景
15 | - 绘制文本(换行、超出内容省略号、中划线、下划线、文本加粗)
16 | - 绘制图片
17 | - 绘制矩形
18 | - 保存图片
19 | - 多图绘制
20 | - ...
21 | - 代码量小
22 |
23 | ## 体验
24 |
25 | ```
26 | git clone https://github.com/kuckboy1994/mp_canvas_drawer
27 | ```
28 | 想在手机上使用配置自己的 `appid` 即可。
29 |
30 | 编译模式中已经为你配置好比较常用的两种模式:
31 | - 普通绘制,绘制单张分享图。
32 | - 多图绘制,连续绘制分享图
33 |
34 | ## 演示
35 |
36 | 
37 |
38 | 左侧是 `canvasdrawer` 绘制的,右侧是UI给的图
39 |
40 | 
41 |
42 | ## 使用
43 |
44 | - `git clone https://github.com/kuckboy1994/mp_canvas_drawer` 到本地
45 | - 把 `components` 中的 `canvasdrawer` 拷贝到自己项目下。
46 | - 在使用页面注册组件
47 | ```json
48 | {
49 | "usingComponents": {
50 | "canvasdrawer": "/components/canvasdrawer/canvasdrawer"
51 | }
52 | }
53 | ```
54 | - 在页面 `**.wxml` 文件中加入如下代码
55 | ```html
56 |
57 | ```
58 | `painting` 是需要传入的 `json`。 `getImage` 方法是绘图完成之后的回调函数,在 `event.detail` 中返回绘制完成的图片地址。
59 | - 当前栗子中的 `painting` 简单展示一下。详细配置请看 [API](https://github.com/kuckboy1994/mp_canvas_drawer#api)
60 |
61 |
62 | painting(点击展开)
63 |
64 | ```js
65 | {
66 | width: 375,
67 | height: 555,
68 | views: [
69 | {
70 | type: 'image',
71 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531103986231.jpeg',
72 | top: 0,
73 | left: 0,
74 | width: 375,
75 | height: 555
76 | },
77 | {
78 | type: 'image',
79 | url: 'https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epJEPdPqQVgv6D8bojGT4DrGXuEC4Oe0GXs5sMsN4GGpCegTUsBgL9SPJkN9UqC1s0iakjQpwd4h4A/132',
80 | top: 27.5,
81 | left: 29,
82 | width: 55,
83 | height: 55
84 | },
85 | {
86 | type: 'image',
87 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531401349117.jpeg',
88 | top: 27.5,
89 | left: 29,
90 | width: 55,
91 | height: 55
92 | },
93 | {
94 | type: 'text',
95 | content: '您的好友【kuckboy】',
96 | fontSize: 16,
97 | color: '#402D16',
98 | textAlign: 'left',
99 | top: 33,
100 | left: 96,
101 | bolder: true
102 | },
103 | {
104 | type: 'text',
105 | content: '发现一件好货,邀请你一起0元免费拿!',
106 | fontSize: 15,
107 | color: '#563D20',
108 | textAlign: 'left',
109 | top: 59.5,
110 | left: 96
111 | },
112 | {
113 | type: 'image',
114 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385366950.jpeg',
115 | top: 136,
116 | left: 42.5,
117 | width: 290,
118 | height: 186
119 | },
120 | {
121 | type: 'image',
122 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385433625.jpeg',
123 | top: 443,
124 | left: 85,
125 | width: 68,
126 | height: 68
127 | },
128 | {
129 | type: 'text',
130 | content: '正品MAC魅可口红礼盒生日唇膏小辣椒Chili西柚情人',
131 | fontSize: 16,
132 | lineHeight: 21,
133 | color: '#383549',
134 | textAlign: 'left',
135 | top: 336,
136 | left: 44,
137 | width: 287,
138 | MaxLineNumber: 2,
139 | breakWord: true,
140 | bolder: true
141 | },
142 | {
143 | type: 'text',
144 | content: '¥0.00',
145 | fontSize: 19,
146 | color: '#E62004',
147 | textAlign: 'left',
148 | top: 387,
149 | left: 44.5,
150 | bolder: true
151 | },
152 | {
153 | type: 'text',
154 | content: '原价:¥138.00',
155 | fontSize: 13,
156 | color: '#7E7E8B',
157 | textAlign: 'left',
158 | top: 391,
159 | left: 110,
160 | textDecoration: 'line-through'
161 | },
162 | {
163 | type: 'text',
164 | content: '长按识别图中二维码帮我砍个价呗~',
165 | fontSize: 14,
166 | color: '#383549',
167 | textAlign: 'left',
168 | top: 460,
169 | left: 165.5,
170 | lineHeight: 20,
171 | MaxLineNumber: 2,
172 | breakWord: true,
173 | width: 125
174 | }
175 | ]
176 | }
177 | ```
178 |
179 |
180 | ## API
181 |
182 | 对象结构一览
183 |
184 | ```js
185 | {
186 | width: 375,
187 | height: 555,
188 | views: [
189 | {
190 | type: 'image',
191 | url: 'url',
192 | top: 0,
193 | left: 0,
194 | width: 375,
195 | height: 555
196 | },
197 | {
198 | type: 'text',
199 | content: 'content',
200 | fontSize: 16,
201 | color: '#402D16',
202 | textAlign: 'left',
203 | top: 33,
204 | left: 96,
205 | bolder: true
206 | },
207 | {
208 | type: 'rect',
209 | background: 'color',
210 | top: 0,
211 | left: 0,
212 | width: 375,
213 | height: 555
214 | }
215 | ]
216 | }
217 | ```
218 |
219 |
220 |
221 | 数据对象的第一层需要三个参数: `width`、`height`、`mode`、`views`。配置中所有的数字都是没有单位的。这就意味着 `canvas` 绘制的是一个比例图。具体显示的大小直接把返回的图片路径放置到 `image` 标签中即可。
222 |
223 | `mode` 可选值有 `same`, 默认值为空,常规下尽量不要使用。如要使用请看 Q&A的第1点。
224 |
225 | 当前可以绘制3种类型的配置: `image`、`text`、`rect`。配置的属性基本上使用的都是 `css` 的驼峰名称,还是比较好理解的。
226 |
227 | ### image(图片)
228 | 属性 | 含义 | 默认值 | 可选值
229 | ---|---|---|---
230 | url | 绘制的图片地址,可以是本地图片,如:`/images/1.jpeg` | |
231 | top | 左上角距离画板顶部的距离 | |
232 | left | 左上角距离画板左侧的距离 | |
233 | width | 要画多宽 | 0 |
234 | height | 要画多高 | 0 |
235 |
236 | ### text(文本)
237 | 属性 | 含义 | 默认值 | 可选值
238 | ---|---|---|---
239 | content | 绘制文本 | ''(空字符串) |
240 | color | 颜色 | black |
241 | fontSize | 字体大小 | 16 |
242 | textAlign | 文字对齐方式 | left | center、right
243 | lineHeight | 行高,只有在多行文本中才有用 | 20 |
244 | top | 文本左上角距离画板顶部的距离 | 0 |
245 | left | 文本左上角距离画板左侧的距离 | 0 |
246 | breakWord | 是否需要换行 | false | true
247 | MaxLineNumber | 最大行数,只有设置 `breakWord: true` ,当前属性才有效,超出行数内容的显示为... | 2 |
248 | width | 和 `MaxLineNumber` 属性配套使用,`width` 就是达到换行的宽度 | |
249 | bolder | 是否加粗 | false | true
250 | textDecoration | 显示中划线、下划线效果 | none | underline(下划线)、line-through(中划线)
251 |
252 | ### rect (矩形,线条)
253 | 属性 | 含义 | 默认值 | 可选值
254 | ---|---|---|---
255 | background | 背景颜色 | black |
256 | top | 左上角距离画板顶部的距离 | |
257 | left | 左上角距离画板左侧的距离 | |
258 | width | 要画多宽 | 0 |
259 | height | 要画多高 | 0 |
260 |
261 | ## Q&A
262 | 0. 最佳实践
263 |
264 | 绘制操作的时候最好 `锁住屏幕` ,例如在点击绘制的时候
265 | ```js
266 | wx.showLoading({
267 | title: '绘制分享图片中',
268 | mask: true
269 | })
270 | ```
271 | 绘制完成之后
272 | ```js
273 | wx.hideLoading()
274 | ```
275 | 具体可以参考项目下的 `/pages/multiple`
276 | 1. [mpvue] 由于 `canvasdrawer` 不主动呈现绘制内容,而是交给调用者去使用 `image` 来展示,所以在mpvue更新数据就会render整个组件的,之后 `canvasdrawer` 又会重新被渲染,导致无限循环,所以默认情况下我把代码改为,传入的 `painting` 和之前的一样的话,组件就不渲染了。只有出现差异的内容才会更新(触发回调),这种个人认为还是可以接受的。
277 | 增加顶层参数 `mode`, `mode: 'same'` 为可以绘制同样的内容。在 `mpvue` 模式下`勿用`。
278 | 2. 二维码和小程序码如何绘制?
279 | - 二维码和小程序码可以通过调用[微信官方的接口](https://developers.weixin.qq.com/miniprogram/dev/api/qrcode.html)产生,需要后端配合。
280 | - 然后走 `type: image` 类型进行绘制即可。
281 | 3. 绘制流程相关
282 | - `views` 数组中的顺序代表绘画的先后顺序,会有覆盖的现象。请各位使用者注意。
283 | 4. 如何实现圆形头像?
284 | - 由于完成一些效果,例如: `文字下划线` 等。必须要使用微信小程序 `rect` 相关的接口,和 `clip` 接口感觉相处的不好(存在bug)。可以查看 [微信小程序社区的帖子](https://developers.weixin.qq.com/blogdetail?action=get_post_info&docid=00086255ef09d0df4b0751f6651000&highline=clip&token=895863755&lang=zh_CN)。
285 | - so,提供一种解决方式:使用一张中间镂空的图片盖在头像上。
286 | 5. `canvas drawer` 组件为什么不直接显示canvas画板和其内容呢?
287 | - 考虑到大部分场景,我们都是用来把图片保存到本地,或用以展示。
288 | - 保存到本地,返回临时文件给调用者一定是最佳的解决方式。
289 | - 展示,转化成图片之后,就可以使用 `image` 基础组件的所有显示模式了,还能设置宽高。
290 |
291 | ## 更新计划
292 |
293 | - [x] 图片缓存机制 - 加快相同图片绘制的速度
294 | - [x] 增加 `measureText` 方法对于低版本的提示
295 | - [x] 安卓下文本绘制稳定性修复
296 | - [ ] 增加圆角属性 `borderRadius`、`border` 支持
297 | - [x] 错误异常回调支持
298 | - [ ] 预缓存模式
299 | - [ ] 优化 `measureText` 测量模式
300 | - [x] mpvue 版本小程序
301 |
302 | ## TIPS
303 |
304 | 如果有什么疑问,欢迎 `issues`。 如果觉得不错,能不能送我小 ✨ ✨ ,让我有更多的动力。
305 |
306 |
307 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({})
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index",
4 | "pages/multiple/multiple"
5 | ],
6 | "window":{
7 | "backgroundTextStyle":"light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "WeChat",
10 | "navigationBarTextStyle":"black"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kuckboy1994/mp_canvas_drawer/18e2274a058b57bd962fd199a0b06d2628dcfa1f/app.wxss
--------------------------------------------------------------------------------
/components/canvasdrawer/canvasdrawer.js:
--------------------------------------------------------------------------------
1 | /* global Component wx */
2 |
3 | Component({
4 | properties: {
5 | painting: {
6 | type: Object,
7 | value: {view: []},
8 | observer (newVal, oldVal) {
9 | if (!this.data.isPainting) {
10 | if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
11 | if (newVal && newVal.width && newVal.height) {
12 | this.setData({
13 | showCanvas: true,
14 | isPainting: true
15 | })
16 | this.readyPigment()
17 | }
18 | } else {
19 | if (newVal && newVal.mode !== 'same') {
20 | this.triggerEvent('getImage', {errMsg: 'canvasdrawer:samme params'})
21 | }
22 | }
23 | }
24 | }
25 | }
26 | },
27 | data: {
28 | showCanvas: false,
29 |
30 | width: 100,
31 | height: 100,
32 |
33 | tempFileList: [],
34 |
35 | isPainting: false
36 | },
37 | ctx: null,
38 | cache: {},
39 | ready () {
40 | wx.removeStorageSync('canvasdrawer_pic_cache')
41 | this.cache = wx.getStorageSync('canvasdrawer_pic_cache') || {}
42 | this.ctx = wx.createCanvasContext('canvasdrawer', this)
43 | },
44 | methods: {
45 | readyPigment () {
46 | const { width, height, views } = this.data.painting
47 | this.setData({
48 | width,
49 | height
50 | })
51 |
52 | const inter = setInterval(() => {
53 | if (this.ctx) {
54 | clearInterval(inter)
55 | this.ctx.clearActions()
56 | this.ctx.save()
57 | this.getImagesInfo(views)
58 | }
59 | }, 100)
60 | },
61 | getImagesInfo (views) {
62 | const imageList = []
63 | for (let i = 0; i < views.length; i++) {
64 | if (views[i].type === 'image') {
65 | imageList.push(this.getImageInfo(views[i].url))
66 | }
67 | }
68 |
69 | const loadTask = []
70 | for (let i = 0; i < Math.ceil(imageList.length / 8); i++) {
71 | loadTask.push(new Promise((resolve, reject) => {
72 | Promise.all(imageList.splice(i * 8, 8)).then(res => {
73 | resolve(res)
74 | }).catch(res => {
75 | reject(res)
76 | })
77 | }))
78 | }
79 | Promise.all(loadTask).then(res => {
80 | let tempFileList = []
81 | for (let i = 0; i < res.length; i++) {
82 | tempFileList = tempFileList.concat(res[i])
83 | }
84 | this.setData({
85 | tempFileList
86 | })
87 | this.startPainting()
88 | })
89 | },
90 | startPainting () {
91 | const { tempFileList, painting: { views } } = this.data
92 | console.log(tempFileList)
93 | for (let i = 0, imageIndex = 0; i < views.length; i++) {
94 | if (views[i].type === 'image') {
95 | this.drawImage({
96 | ...views[i],
97 | url: tempFileList[imageIndex]
98 | })
99 | imageIndex++
100 | } else if (views[i].type === 'text') {
101 | if (!this.ctx.measureText) {
102 | wx.showModal({
103 | title: '提示',
104 | content: '当前微信版本过低,无法使用 measureText 功能,请升级到最新微信版本后重试。'
105 | })
106 | this.triggerEvent('getImage', {errMsg: 'canvasdrawer:version too low'})
107 | return
108 | } else {
109 | this.drawText(views[i])
110 | }
111 | } else if (views[i].type === 'rect') {
112 | this.drawRect(views[i])
113 | }
114 | }
115 | this.ctx.draw(false, () => {
116 | wx.setStorageSync('canvasdrawer_pic_cache', this.cache)
117 | const system = wx.getSystemInfoSync().system
118 | if (/ios/i.test(system)) {
119 | this.saveImageToLocal()
120 | } else {
121 | // 延迟保存图片,解决安卓生成图片错位bug。
122 | setTimeout(() => {
123 | this.saveImageToLocal()
124 | }, 800)
125 | }
126 | })
127 | },
128 | drawImage (params) {
129 | this.ctx.save()
130 | const { url, top = 0, left = 0, width = 0, height = 0, borderRadius = 0, deg = 0 } = params
131 | // if (borderRadius) {
132 | // this.ctx.beginPath()
133 | // this.ctx.arc(left + borderRadius, top + borderRadius, borderRadius, 0, 2 * Math.PI)
134 | // this.ctx.clip()
135 | // this.ctx.drawImage(url, left, top, width, height)
136 | // } else {
137 | if (deg !== 0) {
138 | this.ctx.translate(left + width/2, top + height/2)
139 | this.ctx.rotate(deg * Math.PI / 180)
140 | this.ctx.drawImage(url, -width/2, -height/2, width, height)
141 | } else {
142 | this.ctx.drawImage(url, left, top, width, height)
143 | }
144 | // }
145 | this.ctx.restore()
146 | },
147 | drawText (params) {
148 | this.ctx.save()
149 | const {
150 | MaxLineNumber = 2,
151 | breakWord = false,
152 | color = 'black',
153 | content = '',
154 | fontSize = 16,
155 | top = 0,
156 | left = 0,
157 | lineHeight = 20,
158 | textAlign = 'left',
159 | width,
160 | bolder = false,
161 | textDecoration = 'none'
162 | } = params
163 |
164 | this.ctx.beginPath()
165 | this.ctx.setTextBaseline('top')
166 | this.ctx.setTextAlign(textAlign)
167 | this.ctx.setFillStyle(color)
168 | this.ctx.setFontSize(fontSize)
169 |
170 | if (!breakWord) {
171 | this.ctx.fillText(content, left, top)
172 | this.drawTextLine(left, top, textDecoration, color, fontSize, content)
173 | } else {
174 | let fillText = ''
175 | let fillTop = top
176 | let lineNum = 1
177 | for (let i = 0; i < content.length; i++) {
178 | fillText += [content[i]]
179 | if (this.ctx.measureText(fillText).width > width) {
180 | if (lineNum === MaxLineNumber) {
181 | if (i !== content.length) {
182 | fillText = fillText.substring(0, fillText.length - 1) + '...'
183 | this.ctx.fillText(fillText, left, fillTop)
184 | this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
185 | fillText = ''
186 | break
187 | }
188 | }
189 | this.ctx.fillText(fillText, left, fillTop)
190 | this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
191 | fillText = ''
192 | fillTop += lineHeight
193 | lineNum ++
194 | }
195 | }
196 | this.ctx.fillText(fillText, left, fillTop)
197 | this.drawTextLine(left, fillTop, textDecoration, color, fontSize, fillText)
198 | }
199 |
200 | this.ctx.restore()
201 |
202 | if (bolder) {
203 | this.drawText({
204 | ...params,
205 | left: left + 0.3,
206 | top: top + 0.3,
207 | bolder: false,
208 | textDecoration: 'none'
209 | })
210 | }
211 | },
212 | drawTextLine (left, top, textDecoration, color, fontSize, content) {
213 | if (textDecoration === 'underline') {
214 | this.drawRect({
215 | background: color,
216 | top: top + fontSize * 1.2,
217 | left: left - 1,
218 | width: this.ctx.measureText(content).width + 3,
219 | height: 1
220 | })
221 | } else if (textDecoration === 'line-through') {
222 | this.drawRect({
223 | background: color,
224 | top: top + fontSize * 0.6,
225 | left: left - 1,
226 | width: this.ctx.measureText(content).width + 3,
227 | height: 1
228 | })
229 | }
230 | },
231 | drawRect (params) {
232 | this.ctx.save()
233 | const { background, top = 0, left = 0, width = 0, height = 0 } = params
234 | this.ctx.setFillStyle(background)
235 | this.ctx.fillRect(left, top, width, height)
236 | this.ctx.restore()
237 | },
238 | getImageInfo (url) {
239 | return new Promise((resolve, reject) => {
240 | if (this.cache[url]) {
241 | resolve(this.cache[url])
242 | } else {
243 | const objExp = new RegExp(/^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/)
244 | if (objExp.test(url)) {
245 | wx.getImageInfo({
246 | src: url,
247 | complete: res => {
248 | if (res.errMsg === 'getImageInfo:ok') {
249 | this.cache[url] = res.path
250 | resolve(res.path)
251 | } else {
252 | this.triggerEvent('getImage', {errMsg: 'canvasdrawer:download fail'})
253 | reject(new Error('getImageInfo fail'))
254 | }
255 | }
256 | })
257 | } else {
258 | this.cache[url] = url
259 | resolve(url)
260 | }
261 | }
262 | })
263 | },
264 | saveImageToLocal () {
265 | const { width, height } = this.data
266 | wx.canvasToTempFilePath({
267 | x: 0,
268 | y: 0,
269 | width,
270 | height,
271 | canvasId: 'canvasdrawer',
272 | complete: res => {
273 | if (res.errMsg === 'canvasToTempFilePath:ok') {
274 | this.setData({
275 | showCanvas: false,
276 | isPainting: false,
277 | tempFileList: []
278 | })
279 | this.triggerEvent('getImage', {tempFilePath: res.tempFilePath, errMsg: 'canvasdrawer:ok'})
280 | } else {
281 | this.triggerEvent('getImage', {errMsg: 'canvasdrawer:fail'})
282 | }
283 | }
284 | }, this)
285 | }
286 | }
287 | })
288 |
--------------------------------------------------------------------------------
/components/canvasdrawer/canvasdrawer.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true
3 | }
--------------------------------------------------------------------------------
/components/canvasdrawer/canvasdrawer.wxml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/canvasdrawer/canvasdrawer.wxss:
--------------------------------------------------------------------------------
1 | .board {
2 | position: fixed;
3 | top: 2000rpx;
4 | }
--------------------------------------------------------------------------------
/images/avatar.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kuckboy1994/mp_canvas_drawer/18e2274a058b57bd962fd199a0b06d2628dcfa1f/images/avatar.jpeg
--------------------------------------------------------------------------------
/images/avatar_cover.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kuckboy1994/mp_canvas_drawer/18e2274a058b57bd962fd199a0b06d2628dcfa1f/images/avatar_cover.jpeg
--------------------------------------------------------------------------------
/images/background.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kuckboy1994/mp_canvas_drawer/18e2274a058b57bd962fd199a0b06d2628dcfa1f/images/background.jpeg
--------------------------------------------------------------------------------
/images/pic.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kuckboy1994/mp_canvas_drawer/18e2274a058b57bd962fd199a0b06d2628dcfa1f/images/pic.jpeg
--------------------------------------------------------------------------------
/images/wxacode.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kuckboy1994/mp_canvas_drawer/18e2274a058b57bd962fd199a0b06d2628dcfa1f/images/wxacode.jpeg
--------------------------------------------------------------------------------
/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 |
3 | Page({
4 | data: {
5 | painting: {},
6 | shareImage: ''
7 | },
8 | onLoad () {
9 | // this.eventDraw()
10 | },
11 | eventDraw () {
12 | wx.showLoading({
13 | title: '绘制分享图片中',
14 | mask: true
15 | })
16 | this.setData({
17 | painting: {
18 | width: 375,
19 | height: 555,
20 | clear: true,
21 | views: [
22 | {
23 | type: 'image',
24 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531103986231.jpeg',
25 | top: 0,
26 | left: 0,
27 | width: 375,
28 | height: 555
29 | },
30 | {
31 | type: 'image',
32 | url: 'https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epJEPdPqQVgv6D8bojGT4DrGXuEC4Oe0GXs5sMsN4GGpCegTUsBgL9SPJkN9UqC1s0iakjQpwd4h4A/132',
33 | top: 27.5,
34 | left: 29,
35 | width: 55,
36 | height: 55
37 | },
38 | {
39 | type: 'image',
40 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531401349117.jpeg',
41 | top: 27.5,
42 | left: 29,
43 | width: 55,
44 | height: 55
45 | },
46 | {
47 | type: 'text',
48 | content: '您的好友【kuckboy】',
49 | fontSize: 16,
50 | color: '#402D16',
51 | textAlign: 'left',
52 | top: 33,
53 | left: 96,
54 | bolder: true
55 | },
56 | {
57 | type: 'text',
58 | content: '发现一件好货,邀请你一起0元免费拿!',
59 | fontSize: 15,
60 | color: '#563D20',
61 | textAlign: 'left',
62 | top: 59.5,
63 | left: 96
64 | },
65 | {
66 | type: 'image',
67 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385366950.jpeg',
68 | top: 136,
69 | left: 42.5,
70 | width: 290,
71 | height: 186
72 | },
73 | {
74 | type: 'image',
75 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385433625.jpeg',
76 | top: 443,
77 | left: 85,
78 | width: 68,
79 | height: 68
80 | },
81 | {
82 | type: 'text',
83 | content: '正品MAC魅可口红礼盒生日唇膏小辣椒Chili西柚情人',
84 | fontSize: 16,
85 | lineHeight: 21,
86 | color: '#383549',
87 | textAlign: 'left',
88 | top: 336,
89 | left: 44,
90 | width: 287,
91 | MaxLineNumber: 2,
92 | breakWord: true,
93 | bolder: true
94 | },
95 | {
96 | type: 'text',
97 | content: '¥0.00',
98 | fontSize: 19,
99 | color: '#E62004',
100 | textAlign: 'left',
101 | top: 387,
102 | left: 44.5,
103 | bolder: true
104 | },
105 | {
106 | type: 'text',
107 | content: '原价:¥138.00',
108 | fontSize: 13,
109 | color: '#7E7E8B',
110 | textAlign: 'left',
111 | top: 391,
112 | left: 110,
113 | textDecoration: 'line-through'
114 | },
115 | {
116 | type: 'text',
117 | content: '长按识别图中二维码帮我砍个价呗~',
118 | fontSize: 14,
119 | color: '#383549',
120 | textAlign: 'left',
121 | top: 460,
122 | left: 165.5,
123 | lineHeight: 20,
124 | MaxLineNumber: 2,
125 | breakWord: true,
126 | width: 125
127 | }
128 | ]
129 | }
130 | })
131 | },
132 | eventSave () {
133 | wx.saveImageToPhotosAlbum({
134 | filePath: this.data.shareImage,
135 | success (res) {
136 | wx.showToast({
137 | title: '保存图片成功',
138 | icon: 'success',
139 | duration: 2000
140 | })
141 | }
142 | })
143 | },
144 | eventGetImage (event) {
145 | console.log(event)
146 | wx.hideLoading()
147 | const { tempFilePath, errMsg } = event.detail
148 | if (errMsg === 'canvasdrawer:ok') {
149 | this.setData({
150 | shareImage: tempFilePath
151 | })
152 | }
153 | }
154 | })
155 |
--------------------------------------------------------------------------------
/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "canvas drawer",
3 | "usingComponents": {
4 | "canvasdrawer": "/components/canvasdrawer/canvasdrawer"
5 | }
6 | }
--------------------------------------------------------------------------------
/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | .share-image {
2 | width: 600rpx;
3 | height: 888rpx;
4 | margin: 0 75rpx;
5 | border: 1px solid black;
6 | }
7 |
8 | button {
9 | margin-top: 20rpx;
10 | }
--------------------------------------------------------------------------------
/pages/multiple/multiple.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const app = getApp()
4 |
5 | Page({
6 | data: {
7 | painting: {},
8 | paintingIndex: 0,
9 | paintingList: [
10 | {
11 | width: 375,
12 | height: 555,
13 | clear: true,
14 | views: [
15 | {
16 | type: 'image',
17 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531103986231.jpeg',
18 | top: 0,
19 | left: 0,
20 | width: 375,
21 | height: 555
22 | },
23 | {
24 | type: 'image',
25 | url: 'https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epJEPdPqQVgv6D8bojGT4DrGXuEC4Oe0GXs5sMsN4GGpCegTUsBgL9SPJkN9UqC1s0iakjQpwd4h4A/132',
26 | top: 27.5,
27 | left: 29,
28 | width: 55,
29 | height: 55
30 | },
31 | {
32 | type: 'image',
33 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531401349117.jpeg',
34 | top: 27.5,
35 | left: 29,
36 | width: 55,
37 | height: 55
38 | },
39 | {
40 | type: 'text',
41 | content: '您的好友【kuckboy】',
42 | fontSize: 16,
43 | color: '#402D16',
44 | textAlign: 'left',
45 | top: 33,
46 | left: 96,
47 | bolder: true
48 | },
49 | {
50 | type: 'text',
51 | content: '发现一件好货,邀请你一起0元免费拿!',
52 | fontSize: 15,
53 | color: '#563D20',
54 | textAlign: 'left',
55 | top: 59.5,
56 | left: 96
57 | },
58 | {
59 | type: 'image',
60 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385366950.jpeg',
61 | top: 136,
62 | left: 42.5,
63 | width: 290,
64 | height: 186
65 | },
66 | {
67 | type: 'image',
68 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531385433625.jpeg',
69 | top: 443,
70 | left: 85,
71 | width: 68,
72 | height: 68
73 | },
74 | {
75 | type: 'text',
76 | content: '正品MAC魅可口红礼盒生日唇膏小辣椒Chili西柚情人',
77 | fontSize: 16,
78 | lineHeight: 21,
79 | color: '#383549',
80 | textAlign: 'left',
81 | top: 336,
82 | left: 44,
83 | width: 287,
84 | MaxLineNumber: 2,
85 | breakWord: true,
86 | bolder: true
87 | },
88 | {
89 | type: 'text',
90 | content: '¥0.00',
91 | fontSize: 19,
92 | color: '#E62004',
93 | textAlign: 'left',
94 | top: 387,
95 | left: 44.5,
96 | bolder: true
97 | },
98 | {
99 | type: 'text',
100 | content: '原价:¥138.00',
101 | fontSize: 13,
102 | color: '#7E7E8B',
103 | textAlign: 'left',
104 | top: 391,
105 | left: 110,
106 | textDecoration: 'line-through'
107 | },
108 | {
109 | type: 'text',
110 | content: '长按识别图中二维码帮我砍个价呗~',
111 | fontSize: 14,
112 | color: '#383549',
113 | textAlign: 'left',
114 | top: 460,
115 | left: 165.5,
116 | lineHeight: 20,
117 | MaxLineNumber: 2,
118 | breakWord: true,
119 | width: 125
120 | }
121 | ]
122 | },
123 | {
124 | width: 375,
125 | height: 555,
126 | clear: true,
127 | views: [
128 | {
129 | type: 'image',
130 | url: 'https://hybrid.xiaoying.tv/miniprogram/viva-ad/1/1531447237017.jpeg',
131 | top: 0,
132 | left: 0,
133 | width: 375,
134 | height: 555
135 | }
136 | ]
137 | }
138 | ],
139 | shareImage: '',
140 |
141 | mode: 'normal' // cry
142 | },
143 | eventDraw () {
144 | wx.showLoading({
145 | title: '绘制分享图片中',
146 | mask: true
147 | })
148 | const { paintingList, paintingIndex } = this.data
149 | this.setData({
150 | mode: 'normal',
151 | painting: paintingList[paintingIndex],
152 | paintingIndex: paintingIndex === 0 ? 1 : 0
153 | })
154 | },
155 | eventSave () {
156 | wx.saveImageToPhotosAlbum({
157 | filePath: this.data.shareImage,
158 | success (res) {
159 | wx.showToast({
160 | title: '保存图片成功',
161 | icon: 'success',
162 | duration: 2000
163 | })
164 | }
165 | })
166 | },
167 | eventGetImage (event) {
168 | wx.hideLoading()
169 | const { tempFilePath } = event.detail
170 | this.setData({
171 | shareImage: tempFilePath
172 | })
173 | if (this.data.mode === 'cry') {
174 | this.eventDrawCry()
175 | }
176 | },
177 | eventDrawCry () {
178 | wx.showLoading({
179 | title: '刷新后停止绘制',
180 | mask: true
181 | })
182 | const { paintingList, paintingIndex } = this.data
183 | this.setData({
184 | mode: 'cry',
185 | painting: paintingList[paintingIndex],
186 | paintingIndex: paintingIndex === 0 ? 1 : 0
187 | })
188 | }
189 | })
190 |
--------------------------------------------------------------------------------
/pages/multiple/multiple.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "canvas drawer 多图绘制",
3 | "usingComponents": {
4 | "canvasdrawer": "/components/canvasdrawer/canvasdrawer"
5 | }
6 | }
--------------------------------------------------------------------------------
/pages/multiple/multiple.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/pages/multiple/multiple.wxss:
--------------------------------------------------------------------------------
1 | .share-image {
2 | width: 600rpx;
3 | height: 888rpx;
4 | margin: 0 75rpx;
5 | border: 1px solid black;
6 | }
7 |
8 | button {
9 | margin-top: 20rpx;
10 | }
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件。",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": false,
8 | "es6": true,
9 | "postcss": true,
10 | "minified": true,
11 | "newFeature": true
12 | },
13 | "compileType": "miniprogram",
14 | "libVersion": "2.2.0",
15 | "appid": "",
16 | "projectname": "mp_canvas_drawer",
17 | "isGameTourist": false,
18 | "condition": {
19 | "search": {
20 | "current": -1,
21 | "list": []
22 | },
23 | "conversation": {
24 | "current": -1,
25 | "list": []
26 | },
27 | "plugin": {
28 | "current": -1,
29 | "list": []
30 | },
31 | "game": {
32 | "currentL": -1,
33 | "list": []
34 | },
35 | "miniprogram": {
36 | "current": -1,
37 | "list": [
38 | {
39 | "id": -1,
40 | "name": "多图绘制",
41 | "pathName": "pages/multiple/multiple",
42 | "query": ""
43 | },
44 | {
45 | "id": -1,
46 | "name": "普通绘制",
47 | "pathName": "pages/index/index",
48 | "query": ""
49 | }
50 | ]
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------