├── README.md
├── app.js
├── app.json
├── app.wxss
├── assets
└── test.jpg
├── pages
└── cropper
│ ├── cropper-example.js
│ ├── cropper-example.json
│ ├── cropper-example.wxml
│ ├── cropper-example.wxss
│ ├── cropper.js
│ ├── cropper.json
│ ├── cropper.wxml
│ └── cropper.wxss
├── project.config.json
├── screenshot.gif
├── sitemap.json
└── utils
└── util.js
/README.md:
--------------------------------------------------------------------------------
1 | # wx-cropper
2 | 通用微信小程序图片裁剪组件,简单易用,帮助快速开发图片裁剪功能,支持自定义约束宽高,适用于头像或其他需要特定长宽比图片的场景。
3 | # 裁剪效果预览图
4 | 
5 |
6 | # 用法
7 | 下载源码,pages/cropper/目录拷贝到项目中。
8 | ## 1、在json文件中添加cropper组件
9 | ```
10 | {
11 | "usingComponents": {
12 | "cropper":"/pages/cropper/cropper"
13 | },
14 | "disableScroll": true
15 | }
16 | ```
17 |
18 | ## 2、wxml页面文件中
19 | ```
20 |
21 |
22 |
23 |
24 |
25 | 取消
26 | 确定
27 |
28 |
29 |
30 | ```
31 |
32 | ## 3、在onLoad中初始化组件
33 | ```
34 | cropper = this.selectComponent('#cropper');
35 | cropper.fnInit({
36 | imagePath:'/assets/test.jpg', //*必填
37 | debug: true, //可选。是否启用调试,默认值为false。true:打印过程日志;false:关闭过程日志
38 | outputFileType: 'jpg', //可选。目标文件的类型。默认值为jpg,jpg:输出jpg格式图片;png:输出png格式图片
39 | quality: 1, //可选。图片的质量。默认值为1,即最高质量。目前仅对 jpg 有效。取值范围为 (0, 1],不在范围内时当作 1.0 处理。
40 | aspectRatio: 1.25, //可选。裁剪的宽高比,默认null,即不限制剪裁宽高比。aspectRatio需大于0
41 | minBoxWidthRatio: 0.2, //可选。最小剪裁尺寸与原图尺寸的比率,默认0.15,即宽度最小剪裁到原图的0.15宽。
42 | minBoxHeightRatio: 0.2, //可选。同minBoxWidthRatio,当设置aspectRatio时,minBoxHeight值设置无效。minBoxHeight值由minBoxWidth 和 aspectRatio自动计算得到。
43 | initialBoxWidthRatio: 0.6, //可选。剪裁框初始大小比率。默认值0.6,即剪裁框默认宽度为图片宽度的0.6倍。
44 | initialBoxHeightRatio: 0.6 //可选。同initialBoxWidthRatio,当设置aspectRatio时,initialBoxHeightRatio值设置无效。initialBoxHeightRatio值由initialBoxWidthRatio 和 aspectRatio自动计算得到。
45 | });
46 | ```
47 |
48 | ## 4、在点击事件中裁剪图片,获取裁剪后的图片路径
49 | ```
50 | cropper.fnCrop({
51 | //裁剪成功的回调
52 | success:function(res){
53 | console.log(res)
54 | //生成文件的临时路径
55 | console.log(res.tempFilePath);
56 | wx.previewImage({
57 | urls: [res.tempFilePath],
58 | })
59 | },
60 | //裁剪失败的回调
61 | fail:function(res){
62 | console.log(res);
63 | },
64 | //裁剪结束的回调
65 | complete:function(){
66 | //complete
67 | }
68 | });
69 | ```
70 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 |
4 |
5 |
6 | })
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/cropper/cropper-example"
4 |
5 | ],
6 | "window": {
7 | "backgroundTextStyle": "light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "WeChat",
10 | "navigationBarTextStyle": "black"
11 | },
12 | "sitemapLocation": "sitemap.json"
13 | }
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | padding: 200rpx 0;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/assets/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-wind/wx-cropper/757e3e9743e7dd994274d07ec45f86cd22977f7f/assets/test.jpg
--------------------------------------------------------------------------------
/pages/cropper/cropper-example.js:
--------------------------------------------------------------------------------
1 |
2 | let cropper = null;
3 |
4 | Page({
5 |
6 |
7 | data: {
8 |
9 | },
10 |
11 |
12 | ////////////// init /////////////////////////
13 | onLoad: function (options) {
14 |
15 | cropper = this.selectComponent('#cropper');
16 | cropper.fnInit({
17 | imagePath:'/assets/test.jpg', //*必填
18 | debug: true, //可选。是否启用调试,默认值为false。true:打印过程日志;false:关闭过程日志
19 | outputFileType: 'jpg', //可选。目标文件的类型。默认值为jpg,jpg:输出jpg格式图片;png:输出png格式图片
20 | quality: 1, //可选。图片的质量。默认值为1,即最高质量。目前仅对 jpg 有效。取值范围为 (0, 1],不在范围内时当作 1.0 处理。
21 | aspectRatio: 1.25, //可选。裁剪的宽高比,默认null,即不限制剪裁宽高比。aspectRatio需大于0
22 | minBoxWidthRatio: 0.2, //可选。最小剪裁尺寸与原图尺寸的比率,默认0.15,即宽度最小剪裁到原图的0.15宽。
23 | minBoxHeightRatio: 0.2, //可选。同minBoxWidthRatio,当设置aspectRatio时,minBoxHeight值设置无效。minBoxHeight值由minBoxWidth 和 aspectRatio自动计算得到。
24 | initialBoxWidthRatio: 0.6, //可选。剪裁框初始大小比率。默认值0.6,即剪裁框默认宽度为图片宽度的0.6倍。
25 | initialBoxHeightRatio: 0.6 //可选。同initialBoxWidthRatio,当设置aspectRatio时,initialBoxHeightRatio值设置无效。initialBoxHeightRatio值由initialBoxWidthRatio 和 aspectRatio自动计算得到。
26 | });
27 |
28 |
29 | },
30 |
31 | //////// cancel ///////////////////
32 | fnCancel:function(){
33 | console.log('cancel')
34 | //todo something
35 | },
36 |
37 | ////////// do crop ////////////////////
38 | fnSubmit:function(){
39 | console.log('submit')
40 | //do crop
41 | cropper.fnCrop({
42 |
43 | //剪裁成功的回调
44 | success:function(res){
45 | console.log(res)
46 | //生成文件的临时路径
47 | console.log(res.tempFilePath);
48 | wx.previewImage({
49 | urls: [res.tempFilePath],
50 | })
51 | },
52 | //剪裁失败的回调
53 | fail:function(res){
54 | console.log(res);
55 | },
56 |
57 | //剪裁结束的回调
58 | complete:function(){
59 | //complete
60 | }
61 | });
62 | }
63 | })
--------------------------------------------------------------------------------
/pages/cropper/cropper-example.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "cropper":"/pages/cropper/cropper"
4 | },
5 | "disableScroll": true
6 | }
7 |
--------------------------------------------------------------------------------
/pages/cropper/cropper-example.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 取消
8 | 确定
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/pages/cropper/cropper-example.wxss:
--------------------------------------------------------------------------------
1 | page{
2 | height: 100%;
3 | box-sizing: border-box;
4 | }
5 | .body{
6 | height: 100%;
7 | display: flex;
8 | flex-direction: column;
9 | }
10 |
11 | .stage{
12 | flex-grow: 1;
13 | }
14 |
15 |
16 | .bar{
17 | height: 80rpx;
18 | padding: 36rpx;
19 | display: flex;
20 | flex-direction: row;
21 | background-color: black;
22 | }
23 |
24 | .btn{
25 | position: relative;
26 | flex-grow: 1;
27 | border: 2rpx solid grey;
28 | line-height: 80rpx;
29 | text-align: center;
30 | color: white;
31 | font-size: 32rpx;
32 | }
33 |
34 | .btn-cancel{
35 | border-top-left-radius: 40rpx;
36 | border-bottom-left-radius: 40rpx;
37 | }
38 |
39 | .btn-submit{
40 | border-top-right-radius: 40rpx;
41 | border-bottom-right-radius: 40rpx;
42 | }
--------------------------------------------------------------------------------
/pages/cropper/cropper.js:
--------------------------------------------------------------------------------
1 | let fnLog = function(msg) {
2 | if (debug) {
3 | console.log(msg)
4 | }
5 | }
6 |
7 | //config
8 | let debug = false;//是否启用调试,默认值为false。true:打印过程日志;false:关闭过程日志
9 | let outputFileType = 'jpg';//目标文件的类型。默认值为jpg,jpg:输出jpg格式图片;png:输出png格式图片
10 | let quality = 1;//图片的质量。默认值为1,即最高质量。目前仅对 jpg 有效。取值范围为 (0, 1],不在范围内时当作 1.0 处理。
11 | let aspectRatio = null;//目标图片的宽高比,默认null,即不限制剪裁宽高比。aspectRatio需大于0
12 | //
13 |
14 |
15 | let layoutLeft = 0;
16 | let layoutTop = 0;
17 | let layoutWidth = 0;
18 | let layoutHeight = 0;
19 |
20 | let stageLeft = 0;
21 | let stageTop = 0;
22 | let stageWidth = 0;
23 | let stageHeight = 0;
24 |
25 | let imageWidth = 0;
26 | let imageHeight = 0;
27 |
28 |
29 | let pixelRatio = 1;//todo设备像素密度//暂不使用//
30 |
31 | let imageStageRatio = 1;//图片实际尺寸与剪裁舞台大小的比值,用于尺寸换算。
32 |
33 | let minBoxWidth = 0;
34 | let minBoxHeight = 0;
35 |
36 | //initial
37 | let minBoxWidthRatio = 0.15;//最小剪裁尺寸与原图尺寸的比率,默认0.15,即宽度最小剪裁到原图的0.15宽。
38 | let minBoxHeightRatio = 0.15;//同minBoxWidthRatio,当设置aspectRatio时,minBoxHeight值设置无效。minBoxHeight值由minBoxWidth 和 aspectRatio自动计算得到。
39 |
40 | let initialBoxWidthRatio = 0.6;//剪裁框初始大小比率。默认值0.6,即剪裁框默认宽度为图片宽度的0.6倍。
41 | let initialBoxHeightRatio = 0.6; //同initialBoxWidthRatio,当设置aspectRatio时,initialBoxHeightRatio值设置无效。initialBoxHeightRatio值由initialBoxWidthRatio 和 aspectRatio自动计算得到。
42 | //
43 |
44 |
45 | let touchStartBoxLeft = 0;
46 | let touchStartBoxTop = 0;
47 | let touchStartBoxWidth = 0;
48 | let touchStartBoxHeight = 0;
49 |
50 | let touchStartX = 0;
51 | let touchStartY = 0;
52 |
53 |
54 | Component({
55 |
56 |
57 | /**
58 | * 组件的初始数据
59 | */
60 | data: {
61 |
62 | imagePath: '',
63 |
64 |
65 | stageLeft: 0,
66 | stageTop: 0,
67 | stageWidth: 0,
68 | stageHeight: 0,
69 |
70 | boxWidth: 0,
71 | boxHeight: 0,
72 | boxLeft: 0,
73 | boxTop: 0,
74 |
75 | canvasWidth: 0,
76 | canvasHeight: 0
77 |
78 | },
79 |
80 |
81 |
82 | /**
83 | * 组件的方法列表
84 | */
85 | methods: {
86 |
87 |
88 | fnInit: function(opts) {
89 | let _self = this;
90 |
91 | let imagePath = opts.imagePath;
92 |
93 | if (opts.debug) {
94 | debug = opts.debug;
95 | }
96 |
97 | if (opts.minBoxWidthRatio) {
98 | minBoxWidthRatio = opts.minBoxWidthRatio;
99 | }
100 |
101 | if (opts.minBoxHeightRatio) {
102 | minBoxHeightRatio = opts.minBoxHeightRatio;
103 | }
104 |
105 | if (opts.initialBoxWidthRatio) {
106 | initialBoxWidthRatio = opts.initialBoxWidthRatio;
107 | }
108 |
109 | if (opts.initialBoxHeightRatio) {
110 | initialBoxHeightRatio = opts.initialBoxHeightRatio;
111 | }
112 |
113 | if (opts.aspectRatio) {
114 | aspectRatio = opts.aspectRatio;
115 | }
116 |
117 |
118 |
119 |
120 |
121 | wx.createSelectorQuery().in(this).select('.layout').boundingClientRect(function(rect) {
122 | fnLog(rect);
123 | layoutLeft = rect.left;
124 | layoutTop = rect.top;
125 | layoutWidth = rect.width;
126 | layoutHeight = rect.height;
127 |
128 | wx.getImageInfo({
129 | src: imagePath,
130 | success: function(imageInfo) {
131 | fnLog(imageInfo)
132 | imageWidth = imageInfo.width;
133 | imageHeight = imageInfo.height;
134 |
135 | let imageWH = imageWidth / imageHeight;
136 | let layoutWH = layoutWidth / layoutHeight;
137 | if (imageWH >= layoutWH) {
138 | stageWidth = layoutWidth;
139 | stageHeight = stageWidth / imageWH;
140 | imageStageRatio = imageHeight / stageHeight;
141 | } else {
142 | stageHeight = layoutHeight;
143 | stageWidth = layoutHeight * imageWH;
144 | imageStageRatio = imageWidth / stageWidth;
145 | }
146 | stageLeft = (layoutWidth - stageWidth) / 2;
147 | stageTop = (layoutHeight - stageHeight) / 2;
148 |
149 |
150 | minBoxWidth = stageWidth * minBoxWidthRatio;
151 | minBoxHeight = stageHeight * minBoxHeightRatio;
152 |
153 | let boxWidth = stageWidth * initialBoxWidthRatio;
154 | let boxHeight = stageHeight * initialBoxHeightRatio;
155 |
156 | if (aspectRatio){
157 | boxHeight = boxWidth / aspectRatio;
158 | }
159 | if(boxHeight > stageHeight){
160 | boxHeight = stageHeight;
161 | boxWidth =boxHeight * aspectRatio;
162 | }
163 |
164 | let boxLeft = (stageWidth - boxWidth) / 2;
165 | let boxTop = (stageHeight - boxHeight) / 2;
166 |
167 |
168 | _self.setData({
169 | imagePath: imagePath,
170 | canvasWidth: imageWidth * pixelRatio,
171 | canvasHeight: imageHeight * pixelRatio,
172 |
173 | stageLeft: stageLeft,
174 | stageTop: stageTop,
175 | stageWidth: stageWidth,
176 | stageHeight,
177 | stageHeight,
178 |
179 | boxWidth: boxWidth,
180 | boxHeight: boxHeight,
181 | boxLeft: boxLeft,
182 | boxTop: boxTop
183 | })
184 | }
185 | })
186 |
187 | }).exec();
188 |
189 | },
190 |
191 |
192 | //////////////////////////////////////
193 | fnTouchStart: function(e) {
194 | fnLog('start')
195 | fnLog(e)
196 |
197 | let _self = this;
198 |
199 |
200 | let touch = e.touches[0];
201 | let pageX = touch.pageX;
202 | let pageY = touch.pageY;
203 |
204 | touchStartX = pageX;
205 | touchStartY = pageY;
206 |
207 | touchStartBoxLeft = _self.data.boxLeft;
208 | touchStartBoxTop = _self.data.boxTop;
209 | touchStartBoxWidth = _self.data.boxWidth;
210 | touchStartBoxHeight = _self.data.boxHeight;
211 |
212 |
213 | },
214 |
215 | fnTouchMove: function(e) {
216 | fnLog('move')
217 | fnLog(e)
218 | let _self = this;
219 |
220 | let targetId = e.target.id;
221 | let touch = e.touches[0];
222 | let pageX = touch.pageX;
223 | let pageY = touch.pageY;
224 |
225 | let offsetX = pageX - touchStartX;
226 | let offsetY = pageY - touchStartY;
227 |
228 |
229 |
230 |
231 |
232 | if (targetId == 'box') {
233 | let newBoxLeft = touchStartBoxLeft + offsetX;
234 | let newBoxTop = touchStartBoxTop + offsetY;
235 |
236 | if (newBoxLeft < 0) {
237 | newBoxLeft = 0;
238 | }
239 | if (newBoxTop < 0) {
240 | newBoxTop = 0;
241 | }
242 | if (newBoxLeft + touchStartBoxWidth > stageWidth) {
243 | newBoxLeft = stageWidth - touchStartBoxWidth;
244 | }
245 | if (newBoxTop + touchStartBoxHeight > stageHeight) {
246 | newBoxTop = stageHeight - touchStartBoxHeight;
247 | }
248 | _self.setData({
249 | boxLeft: newBoxLeft,
250 | boxTop: newBoxTop
251 | });
252 | } else if (targetId == 'lt') {
253 |
254 | if (aspectRatio) {
255 | offsetY = offsetX / aspectRatio
256 | }
257 |
258 | let newBoxLeft = touchStartBoxLeft + offsetX;
259 | let newBoxTop = touchStartBoxTop + offsetY;
260 |
261 | if (newBoxLeft < 0) {
262 | newBoxLeft = 0;
263 | }
264 | if (newBoxTop < 0) {
265 | newBoxTop = 0;
266 | }
267 |
268 | if ((touchStartBoxLeft + touchStartBoxWidth - newBoxLeft) < minBoxWidth) {
269 | newBoxLeft = touchStartBoxLeft + touchStartBoxWidth - minBoxWidth;
270 | }
271 | if ((touchStartBoxTop + touchStartBoxHeight - newBoxTop) < minBoxHeight) {
272 | newBoxTop = touchStartBoxTop + touchStartBoxHeight - minBoxHeight;
273 | }
274 |
275 | let newBoxWidth = touchStartBoxWidth - (newBoxLeft - touchStartBoxLeft);
276 | let newBoxHeight = touchStartBoxHeight - (newBoxTop - touchStartBoxTop);
277 |
278 |
279 | //约束比例
280 | if (newBoxTop == 0 && aspectRatio && newBoxLeft != 0) {
281 | newBoxWidth = newBoxHeight * aspectRatio;
282 | newBoxLeft = touchStartBoxWidth - newBoxWidth + touchStartBoxLeft;
283 | }
284 | if(newBoxLeft == 0 && aspectRatio){
285 | newBoxHeight = newBoxWidth / aspectRatio;
286 | newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
287 | }
288 |
289 | if (newBoxWidth == minBoxWidth && aspectRatio) {
290 | newBoxHeight = newBoxWidth / aspectRatio;
291 | newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
292 | }
293 |
294 | _self.setData({
295 | boxTop: newBoxTop,
296 | boxLeft: newBoxLeft,
297 | boxWidth: newBoxWidth,
298 | boxHeight: newBoxHeight
299 | });
300 |
301 | } else if (targetId == 'rt') {
302 |
303 | if (aspectRatio) {
304 | offsetY = -offsetX / aspectRatio
305 | }
306 |
307 |
308 |
309 | let newBoxWidth = touchStartBoxWidth + offsetX;
310 | if (newBoxWidth < minBoxWidth) {
311 | newBoxWidth = minBoxWidth;
312 | }
313 | if (touchStartBoxLeft + newBoxWidth > stageWidth) {
314 | newBoxWidth = stageWidth - touchStartBoxLeft;
315 | }
316 |
317 |
318 | let newBoxTop = touchStartBoxTop + offsetY;
319 |
320 | if (newBoxTop < 0) {
321 | newBoxTop = 0;
322 | }
323 |
324 | if ((touchStartBoxTop + touchStartBoxHeight - newBoxTop) < minBoxHeight) {
325 | newBoxTop = touchStartBoxTop + touchStartBoxHeight - minBoxHeight;
326 | }
327 | let newBoxHeight = touchStartBoxHeight - (newBoxTop - touchStartBoxTop);
328 |
329 |
330 | //约束比例
331 | if (newBoxTop == 0 && aspectRatio && newBoxWidth != stageWidth - touchStartBoxLeft) {
332 | newBoxWidth = newBoxHeight * aspectRatio;
333 | }
334 |
335 | if (newBoxWidth == stageWidth - touchStartBoxLeft && aspectRatio) {
336 | newBoxHeight = newBoxWidth / aspectRatio;
337 | newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
338 | }
339 |
340 | if (newBoxWidth == minBoxWidth && aspectRatio) {
341 | newBoxHeight = newBoxWidth / aspectRatio;
342 | newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop;
343 | }
344 |
345 |
346 |
347 |
348 | _self.setData({
349 | boxTop: newBoxTop,
350 | boxHeight: newBoxHeight,
351 | boxWidth: newBoxWidth
352 | });
353 | } else if (targetId == 'lb') {
354 |
355 | if (aspectRatio) {
356 | offsetY = -offsetX / aspectRatio
357 | }
358 | let newBoxLeft = touchStartBoxLeft + offsetX;
359 |
360 | if (newBoxLeft < 0) {
361 | newBoxLeft = 0;
362 | }
363 | if ((touchStartBoxLeft + touchStartBoxWidth - newBoxLeft) < minBoxWidth) {
364 | newBoxLeft = touchStartBoxLeft + touchStartBoxWidth - minBoxWidth;
365 | }
366 |
367 | let newBoxWidth = touchStartBoxWidth - (newBoxLeft - touchStartBoxLeft);
368 |
369 |
370 | let newBoxHeight = touchStartBoxHeight + offsetY;
371 | if (newBoxHeight < minBoxHeight) {
372 | newBoxHeight = minBoxHeight;
373 | }
374 | if (touchStartBoxTop + newBoxHeight > stageHeight) {
375 | newBoxHeight = stageHeight - touchStartBoxTop;
376 | }
377 |
378 | //约束比例
379 | if (newBoxHeight == stageHeight - touchStartBoxTop && aspectRatio && newBoxLeft != 0) {
380 | newBoxWidth = newBoxHeight * aspectRatio;
381 | newBoxLeft = touchStartBoxWidth - newBoxWidth + touchStartBoxLeft;
382 | }
383 | if(newBoxLeft == 0 && aspectRatio){
384 | newBoxHeight = newBoxWidth / aspectRatio;
385 | }
386 |
387 | if (newBoxWidth == minBoxWidth && aspectRatio) {
388 | newBoxHeight = newBoxWidth / aspectRatio;
389 | }
390 |
391 |
392 |
393 |
394 | _self.setData({
395 |
396 | boxLeft: newBoxLeft,
397 | boxWidth: newBoxWidth,
398 | boxHeight: newBoxHeight
399 | });
400 | } else if (targetId == 'rb') {
401 | if (aspectRatio) {
402 | offsetY = offsetX / aspectRatio
403 | }
404 | let newBoxWidth = touchStartBoxWidth + offsetX;
405 | if (newBoxWidth < minBoxWidth) {
406 | newBoxWidth = minBoxWidth;
407 | }
408 | if (touchStartBoxLeft + newBoxWidth > stageWidth) {
409 | newBoxWidth = stageWidth - touchStartBoxLeft;
410 | }
411 |
412 | let newBoxHeight = touchStartBoxHeight + offsetY;
413 | if (newBoxHeight < minBoxHeight) {
414 | newBoxHeight = minBoxHeight;
415 | }
416 | if (touchStartBoxTop + newBoxHeight > stageHeight) {
417 | newBoxHeight = stageHeight - touchStartBoxTop;
418 | }
419 |
420 |
421 | //约束比例
422 | if (newBoxHeight == stageHeight - touchStartBoxTop && aspectRatio && newBoxWidth != stageWidth - touchStartBoxLeft) {
423 | newBoxWidth = newBoxHeight * aspectRatio;
424 | }
425 |
426 | if (newBoxWidth == stageWidth - touchStartBoxLeft && aspectRatio){
427 | newBoxHeight = newBoxWidth / aspectRatio;
428 | }
429 |
430 | if(newBoxWidth == minBoxWidth && aspectRatio){
431 | newBoxHeight = newBoxWidth / aspectRatio;
432 | }
433 |
434 |
435 | _self.setData({
436 |
437 | boxWidth: newBoxWidth,
438 | boxHeight: newBoxHeight
439 | });
440 |
441 | }
442 |
443 |
444 | },
445 |
446 | fnTouchEnd: function(e) {
447 | fnLog('end')
448 | },
449 |
450 | fnTouchCancel: function(e) {
451 | fnLog('cancel')
452 | },
453 |
454 |
455 | fnCrop: function(opts) {
456 | let _self = this;
457 |
458 |
459 | let _success = function() {};
460 | let _fail = function() {};
461 | let _complete = function() {};
462 |
463 | if (opts.success != null) {
464 | _success = opts.success;
465 | }
466 | if (opts.fail != null) {
467 | _fail = opts.fail;
468 | }
469 | if (opts.complete != null) {
470 | _complete = opts.complete;
471 | }
472 |
473 |
474 |
475 |
476 | let imagePath = _self.data.imagePath;
477 | let canvasContext = wx.createCanvasContext('canvas', _self);
478 |
479 |
480 |
481 | let boxLeft = _self.data.boxLeft;
482 | let boxTop = _self.data.boxTop;
483 | let boxWidth = _self.data.boxWidth;
484 | let boxHeight = _self.data.boxHeight;
485 |
486 | let sx = Math.ceil(boxLeft * imageStageRatio);
487 | let sy = Math.ceil(boxTop * imageStageRatio);
488 |
489 | let sWidth = Math.ceil(boxWidth * imageStageRatio);
490 | let sHeight = Math.ceil(boxHeight * imageStageRatio);
491 | let dx = 0;
492 | let dy = 0;
493 |
494 |
495 |
496 | let dWidth = Math.ceil(sWidth * pixelRatio);
497 | let dHeight = Math.ceil(sHeight * pixelRatio);
498 |
499 |
500 |
501 | canvasContext.drawImage(imagePath, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
502 | canvasContext.draw(false, function() {
503 | wx.canvasToTempFilePath({
504 | x: dx,
505 | y: dy,
506 | width: dWidth,
507 | height: dHeight,
508 | destWidth: sWidth,
509 | destHeight: sHeight,
510 | canvasId: 'canvas',
511 | fileType: outputFileType,
512 | quality: quality,
513 | success: _success,
514 | fail: _fail,
515 | complete: _complete
516 | }, _self);
517 | })
518 |
519 |
520 | }
521 |
522 |
523 | }
524 |
525 |
526 | })
--------------------------------------------------------------------------------
/pages/cropper/cropper.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/pages/cropper/cropper.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/pages/cropper/cropper.wxss:
--------------------------------------------------------------------------------
1 | /* pages/cutter/cutter.wxss */
2 | :host{
3 | position: relative;
4 | }
5 |
6 |
7 | .layout{
8 | position: absolute;
9 | left: 0rpx;
10 | right: 0rpx;
11 | top: 0rpx;
12 | bottom: 0rpx;
13 | background-color: black;
14 | }
15 |
16 | .stage{
17 | position: absolute;
18 | }
19 |
20 | .image{
21 | position: absolute;
22 | width: 100%;
23 | height: 100%;
24 | }
25 |
26 |
27 |
28 | .box{
29 | position: absolute;
30 | border: 4rpx solid grey;
31 | box-sizing: border-box;
32 | box-shadow: 0rpx 0rpx 0rpx 2000rpx rgba(0, 0, 0, 0.5)
33 | }
34 |
35 |
36 | .lt{
37 | position: absolute;
38 | height: 48rpx;
39 | width: 48rpx;
40 | left: -6rpx;
41 | top: -6rpx;
42 | border-left: 12rpx solid white;
43 | border-top: 12rpx solid white;
44 | }
45 |
46 | .lb{
47 | position: absolute;
48 | height: 48rpx;
49 | width: 48rpx;
50 | left: -6rpx;
51 | bottom: -6rpx;
52 | border-left: 12rpx solid white;
53 | border-bottom: 12rpx solid white;
54 | }
55 |
56 | .rt{
57 | position: absolute;
58 | height: 48rpx;
59 | width: 48rpx;
60 | right: -6rpx;
61 | top: -6rpx;
62 | border-right: 12rpx solid white;
63 | border-top: 12rpx solid white;
64 | }
65 |
66 | .rb{
67 | position: absolute;
68 | height: 48rpx;
69 | width: 48rpx;
70 | right: -6rpx;
71 | bottom: -6rpx;
72 | border-right: 12rpx solid white;
73 | border-bottom: 12rpx solid white;
74 | }
75 |
76 | .line-v{
77 | position: absolute;
78 | width: 2rpx;
79 | height: 100%;
80 | background-color: rgba(255, 255, 255, 0.5);
81 | }
82 | .line-h{
83 | height: 2rpx;
84 | position: absolute;
85 | width: 100%;
86 | background-color: rgba(255, 255, 255, 0.5);
87 | }
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件",
3 | "packOptions": {
4 | "ignore": []
5 | },
6 | "setting": {
7 | "urlCheck": true,
8 | "es6": true,
9 | "postcss": true,
10 | "minified": true,
11 | "newFeature": true,
12 | "autoAudits": false
13 | },
14 | "compileType": "miniprogram",
15 | "libVersion": "2.0.4",
16 | "appid": "wx8115d6d888d52e47",
17 | "projectname": "capt-mini-components",
18 | "debugOptions": {
19 | "hidedInDevtools": []
20 | },
21 | "isGameTourist": false,
22 | "simulatorType": "wechat",
23 | "simulatorPluginLibVersion": {},
24 | "condition": {
25 | "search": {
26 | "current": -1,
27 | "list": []
28 | },
29 | "conversation": {
30 | "current": -1,
31 | "list": []
32 | },
33 | "game": {
34 | "currentL": -1,
35 | "list": []
36 | },
37 | "miniprogram": {
38 | "current": -1,
39 | "list": []
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/light-wind/wx-cropper/757e3e9743e7dd994274d07ec45f86cd22977f7f/screenshot.gif
--------------------------------------------------------------------------------
/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------