├── src
├── image-cropper.json
├── image-cropper.wxml
├── image-cropper.wxss
└── image-cropper.js
├── demo
├── app.wxss
├── component
│ └── image-cropper
│ │ ├── image-cropper.json
│ │ ├── image-cropper.wxml
│ │ ├── image-cropper.wxss
│ │ └── image-cropper.js
├── app.js
├── sitemap.json
├── index
│ ├── index.json
│ ├── index.wxml
│ ├── index.wxss
│ └── index.js
├── app.json
├── cropper
│ ├── cropper.json
│ ├── cropper.wxss
│ ├── cropper.wxml
│ └── cropper.js
└── project.config.json
├── .github
└── ISSUE_TEMPLATE
│ ├── encourage.jpg
│ └── bug_report.md
├── .gitignore
├── LICENSE
└── README.md
/src/image-cropper.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true
3 | }
--------------------------------------------------------------------------------
/demo/app.wxss:
--------------------------------------------------------------------------------
1 | page{
2 | height: 100%;
3 | width: 100%;
4 | }
--------------------------------------------------------------------------------
/demo/component/image-cropper/image-cropper.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true
3 | }
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | App({
2 | onLaunch: function () {
3 |
4 | },
5 | globalData:{}
6 | })
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/encourage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1977474741/image-cropper/HEAD/.github/ISSUE_TEMPLATE/encourage.jpg
--------------------------------------------------------------------------------
/demo/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/demo/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "navigationBarTitleText": "image-cropper",
4 | "navigationBarBackgroundColor": "#292929",
5 | "navigationBarTextStyle": "white",
6 | "backgroundColor": "#292929"
7 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 提交bug
3 | about: 按照这个模板写
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | 对应工具或者iOS或者Andriod的版本号
11 |
12 | 微信版本号
13 |
14 | 代码截图
15 |
16 | 重现步骤
17 |
18 | 期待的行为
19 |
20 | 实际的行为
21 |
--------------------------------------------------------------------------------
/demo/index/index.wxml:
--------------------------------------------------------------------------------
1 |

112 |
--------------------------------------------------------------------------------
/demo/cropper/cropper.js:
--------------------------------------------------------------------------------
1 | //获取应用实例
2 | const app = getApp()
3 | Page({
4 | data: {
5 | src: '',
6 | width: 250, //宽度
7 | height: 250, //高度
8 | max_width: 300,
9 | max_height: 300,
10 | disable_rotate: true, //是否禁用旋转
11 | disable_ratio: false, //锁定比例
12 | limit_move: true, //是否限制移动
13 | },
14 | onLoad: function (options) {
15 | this.cropper = this.selectComponent("#image-cropper");
16 | this.setData({
17 | src: options.imgSrc
18 | });
19 | if(!options.imgSrc){
20 | this.cropper.upload(); //上传图片
21 | }
22 | },
23 | cropperload(e) {
24 | console.log('cropper加载完成');
25 | },
26 | loadimage(e) {
27 | wx.hideLoading();
28 | console.log('图片');
29 | this.cropper.imgReset();
30 | },
31 | clickcut(e) {
32 | console.log(e.detail);
33 | //图片预览
34 | wx.previewImage({
35 | current: e.detail.url, // 当前显示图片的http链接
36 | urls: [e.detail.url] // 需要预览的图片http链接列表
37 | })
38 | },
39 | upload() {
40 | let that = this;
41 | wx.chooseImage({
42 | count: 1,
43 | sizeType: ['original', 'compressed'],
44 | sourceType: ['album', 'camera'],
45 | success(res) {
46 | wx.showLoading({
47 | title: '加载中',
48 | })
49 | const tempFilePaths = res.tempFilePaths[0];
50 | //重置图片角度、缩放、位置
51 | that.cropper.imgReset();
52 | that.setData({
53 | src: tempFilePaths
54 | });
55 | }
56 | })
57 | },
58 | setWidth(e) {
59 | this.setData({
60 | width: e.detail.value < 10 ? 10 : e.detail.value
61 | });
62 | this.setData({
63 | cut_left: this.cropper.data.cut_left
64 | });
65 | },
66 | setHeight(e) {
67 | this.setData({
68 | height: e.detail.value < 10 ? 10 : e.detail.value
69 | });
70 | this.setData({
71 | cut_top: this.cropper.data.cut_top
72 | });
73 | },
74 | switchChangeDisableRatio(e) {
75 | //设置宽度之后使剪裁框居中
76 | this.setData({
77 | disable_ratio: e.detail.value
78 | });
79 | },
80 | setCutTop(e) {
81 | this.setData({
82 | cut_top: e.detail.value
83 | });
84 | this.setData({
85 | cut_top: this.cropper.data.cut_top
86 | });
87 | },
88 | setCutLeft(e) {
89 | this.setData({
90 | cut_left: e.detail.value
91 | });
92 | this.setData({
93 | cut_left: this.cropper.data.cut_left
94 | });
95 | },
96 | switchChangeDisableRotate(e) {
97 | //开启旋转的同时不限制移动
98 | if (!e.detail.value) {
99 | this.setData({
100 | limit_move: false,
101 | disable_rotate: e.detail.value
102 | });
103 | } else {
104 | this.setData({
105 | disable_rotate: e.detail.value
106 | });
107 | }
108 | },
109 | switchChangeLimitMove(e) {
110 | //限制移动的同时锁定旋转
111 | if (e.detail.value) {
112 | this.setData({
113 | disable_rotate: true
114 | });
115 | }
116 | this.cropper.setLimitMove(e.detail.value);
117 | },
118 | switchChangeDisableWidth(e) {
119 | this.setData({
120 | disable_width: e.detail.value
121 | });
122 | },
123 | switchChangeDisableHeight(e) {
124 | this.setData({
125 | disable_height: e.detail.value
126 | });
127 | },
128 | submit() {
129 | this.cropper.getImg((obj) => {
130 | app.globalData.imgSrc = obj.url;
131 | wx.navigateBack({
132 | delta: -1
133 | })
134 | });
135 | },
136 | rotate() {
137 | //在用户旋转的基础上旋转90°
138 | this.cropper.setAngle(this.cropper.data.angle += 90);
139 | },
140 | top() {
141 | this.data.top = setInterval(() => {
142 | this.cropper.setTransform({
143 | y: -3
144 | });
145 | }, 1000 / 60)
146 | },
147 | bottom() {
148 | this.data.bottom = setInterval(() => {
149 | this.cropper.setTransform({
150 | y: 3
151 | });
152 | }, 1000 / 60)
153 | },
154 | left() {
155 | this.data.left = setInterval(() => {
156 | this.cropper.setTransform({
157 | x: -3
158 | });
159 | }, 1000 / 60)
160 | },
161 | right() {
162 | this.data.right = setInterval(() => {
163 | this.cropper.setTransform({
164 | x: 3
165 | });
166 | }, 1000 / 60)
167 | },
168 | narrow() {
169 | this.data.narrow = setInterval(() => {
170 | this.cropper.setTransform({
171 | scale: -0.02
172 | });
173 | }, 1000 / 60)
174 | },
175 | enlarge() {
176 | this.data.enlarge = setInterval(() => {
177 | this.cropper.setTransform({
178 | scale: 0.02
179 | });
180 | }, 1000 / 60)
181 | },
182 | end(e) {
183 | clearInterval(this.data[e.currentTarget.dataset.type]);
184 | },
185 | })
--------------------------------------------------------------------------------
/src/image-cropper.js:
--------------------------------------------------------------------------------
1 | Component({
2 | properties: {
3 | /**
4 | * 图片路径
5 | */
6 | 'imgSrc': {
7 | type: String
8 | },
9 | /**
10 | * 裁剪框高度
11 | */
12 | 'height': {
13 | type: Number,
14 | value: 200
15 | },
16 | /**
17 | * 裁剪框宽度
18 | */
19 | 'width': {
20 | type: Number,
21 | value: 200
22 | },
23 | /**
24 | * 裁剪框最小尺寸
25 | */
26 | 'min_width': {
27 | type: Number,
28 | value: 100
29 | },
30 | 'min_height': {
31 | type: Number,
32 | value: 100
33 | },
34 | /**
35 | * 裁剪框最大尺寸
36 | */
37 | 'max_width': {
38 | type: Number,
39 | value: 300
40 | },
41 | 'max_height': {
42 | type: Number,
43 | value: 300
44 | },
45 | /**
46 | * 裁剪框禁止拖动
47 | */
48 | 'disable_width': {
49 | type: Boolean,
50 | value: false
51 | },
52 | 'disable_height': {
53 | type: Boolean,
54 | value: false
55 | },
56 | /**
57 | * 锁定裁剪框比例
58 | */
59 | 'disable_ratio': {
60 | type: Boolean,
61 | value: false
62 | },
63 | /**
64 | * 生成的图片尺寸相对剪裁框的比例
65 | */
66 | 'export_scale': {
67 | type: Number,
68 | value: 3
69 | },
70 | /**
71 | * 生成的图片质量0-1
72 | */
73 | 'quality': {
74 | type: Number,
75 | value: 1
76 | },
77 | 'cut_top': {
78 | type: Number,
79 | value: null
80 | },
81 | 'cut_left': {
82 | type: Number,
83 | value: null
84 | },
85 | /**
86 | * canvas上边距(不设置默认不显示)
87 | */
88 | 'canvas_top': {
89 | type: Number,
90 | value: null
91 | },
92 | /**
93 | * canvas左边距(不设置默认不显示)
94 | */
95 | 'canvas_left': {
96 | type: Number,
97 | value: null
98 | },
99 | /**
100 | * 图片宽度
101 | */
102 | 'img_width': {
103 | type: null,
104 | value: null
105 | },
106 | /**
107 | * 图片高度
108 | */
109 | 'img_height': {
110 | type: null,
111 | value: null
112 | },
113 | /**
114 | * 图片缩放比
115 | */
116 | 'scale': {
117 | type: Number,
118 | value: 1
119 | },
120 | /**
121 | * 图片旋转角度
122 | */
123 | 'angle': {
124 | type: Number,
125 | value: 0
126 | },
127 | /**
128 | * 最小缩放比
129 | */
130 | 'min_scale': {
131 | type: Number,
132 | value: 0.5
133 | },
134 | /**
135 | * 最大缩放比
136 | */
137 | 'max_scale': {
138 | type: Number,
139 | value: 2
140 | },
141 | /**
142 | * 是否禁用旋转
143 | */
144 | 'disable_rotate': {
145 | type: Boolean,
146 | value: false
147 | },
148 | /**
149 | * 是否限制移动范围(剪裁框只能在图片内)
150 | */
151 | 'limit_move': {
152 | type: Boolean,
153 | value: false
154 | }
155 | },
156 | data: {
157 | el: 'image-cropper', //暂时无用
158 | info: wx.getSystemInfoSync(),
159 | MOVE_THROTTLE: null, //触摸移动节流settimeout
160 | MOVE_THROTTLE_FLAG: true, //节流标识
161 | INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
162 | INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
163 | TIME_BG: null, //背景变暗延时函数
164 | TIME_CUT_CENTER: null,
165 | _touch_img_relative: [{
166 | x: 0,
167 | y: 0
168 | }], //鼠标和图片中心的相对位置
169 | _flag_cut_touch: false, //是否是拖动裁剪框
170 | _hypotenuse_length: 0, //双指触摸时斜边长度
171 | _flag_img_endtouch: false, //是否结束触摸
172 | _flag_bright: true, //背景是否亮
173 | _canvas_overflow: true, //canvas缩略图是否在屏幕外面
174 | _canvas_width: 200,
175 | _canvas_height: 200,
176 | origin_x: 0.5, //图片旋转中心
177 | origin_y: 0.5, //图片旋转中心
178 | _cut_animation: false, //是否开启图片和裁剪框过渡
179 | _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距
180 | _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距
181 | watch: {
182 | //监听截取框宽高变化
183 | width(value, that) {
184 | if (value < that.data.min_width) {
185 | that.setData({
186 | width: that.data.min_width
187 | });
188 | }
189 | that._computeCutSize();
190 | },
191 | height(value, that) {
192 | if (value < that.data.min_height) {
193 | that.setData({
194 | height: that.data.min_height
195 | });
196 | }
197 | that._computeCutSize();
198 | },
199 | angle(value, that) {
200 | //停止居中裁剪框,继续修改图片位置
201 | that._moveStop();
202 | if (that.data.limit_move) {
203 | if (that.data.angle % 90) {
204 | that.setData({
205 | angle: Math.round(that.data.angle / 90) * 90
206 | });
207 | return;
208 | }
209 | }
210 | },
211 | _cut_animation(value, that) {
212 | //开启过渡300毫秒之后自动关闭
213 | clearTimeout(that.data._cut_animation_time);
214 | if (value) {
215 | that.data._cut_animation_time = setTimeout(() => {
216 | that.setData({
217 | _cut_animation: false
218 | });
219 | }, 300)
220 | }
221 | },
222 | limit_move(value, that) {
223 | if (value) {
224 | if (that.data.angle % 90) {
225 | that.setData({
226 | angle: Math.round(that.data.angle / 90) * 90
227 | });
228 | }
229 | that._imgMarginDetectionScale();
230 | !that.data._canvas_overflow && that._draw();
231 | }
232 | },
233 | canvas_top(value, that) {
234 | that._canvasDetectionPosition();
235 | },
236 | canvas_left(value, that) {
237 | that._canvasDetectionPosition();
238 | },
239 | imgSrc(value, that) {
240 | that.pushImg();
241 | },
242 | cut_top(value, that) {
243 | that._cutDetectionPosition();
244 | if (that.data.limit_move) {
245 | !that.data._canvas_overflow && that._draw();
246 | }
247 | },
248 | cut_left(value, that) {
249 | that._cutDetectionPosition();
250 | if (that.data.limit_move) {
251 | !that.data._canvas_overflow && that._draw();
252 | }
253 | }
254 | }
255 | },
256 | attached() {
257 | this.data.info = wx.getSystemInfoSync();
258 | //启用数据监听
259 | this._watcher();
260 | this.data.INIT_IMGWIDTH = this.data.img_width;
261 | this.data.INIT_IMGHEIGHT = this.data.img_height;
262 | this.setData({
263 | _canvas_height: this.data.height,
264 | _canvas_width: this.data.width,
265 | });
266 | this._initCanvas();
267 | this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc);
268 | //根据开发者设置的图片目标尺寸计算实际尺寸
269 | this._initImageSize();
270 | //设置裁剪框大小>设置图片尺寸>绘制canvas
271 | this._computeCutSize();
272 | //检查裁剪框是否在范围内
273 | this._cutDetectionPosition();
274 | //检查canvas是否在范围内
275 | this._canvasDetectionPosition();
276 | //初始化完成
277 | this.triggerEvent('load', {
278 | cropper: this
279 | });
280 | },
281 | methods: {
282 | /**
283 | * 上传图片
284 | */
285 | upload() {
286 | let that = this;
287 | wx.chooseImage({
288 | count: 1,
289 | sizeType: ['original', 'compressed'],
290 | sourceType: ['album', 'camera'],
291 | success(res) {
292 | const tempFilePaths = res.tempFilePaths[0];
293 | that.pushImg(tempFilePaths);
294 | wx.showLoading({
295 | title: '加载中...'
296 | })
297 | }
298 | })
299 | },
300 | /**
301 | * 返回图片信息
302 | */
303 | getImg(getCallback) {
304 | this._draw(() => {
305 | wx.canvasToTempFilePath({
306 | width: this.data.width * this.data.export_scale,
307 | height: Math.round(this.data.height * this.data.export_scale),
308 | destWidth: this.data.width * this.data.export_scale,
309 | destHeight: Math.round(this.data.height) * this.data.export_scale,
310 | fileType: 'png',
311 | quality: this.data.quality,
312 | canvasId: this.data.el,
313 | success: (res) => {
314 | getCallback({
315 | url: res.tempFilePath,
316 | width: this.data.width * this.data.export_scale,
317 | height: this.data.height * this.data.export_scale
318 | });
319 | }
320 | }, this)
321 | });
322 | },
323 | /**
324 | * 设置图片动画
325 | * {
326 | * x:10,//图片在原有基础上向下移动10px
327 | * y:10,//图片在原有基础上向右移动10px
328 | * angle:10,//图片在原有基础上旋转10deg
329 | * scale:0.5,//图片在原有基础上增加0.5倍
330 | * }
331 | */
332 | setTransform(transform) {
333 | if (!transform) return;
334 | if (!this.data.disable_rotate) {
335 | this.setData({
336 | angle: transform.angle ? this.data.angle + transform.angle : this.data.angle
337 | });
338 | }
339 | var scale = this.data.scale;
340 | if (transform.scale) {
341 | scale = this.data.scale + transform.scale;
342 | scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
343 | scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
344 | }
345 | this.data.scale = scale;
346 | let cutX = this.data.cut_left;
347 | let cutY = this.data.cut_top;
348 | if (transform.cutX) {
349 | this.setData({
350 | cut_left: cutX + transform.cutX
351 | });
352 | this.data.watch.cut_left(null, this);
353 | }
354 | if (transform.cutY) {
355 | this.setData({
356 | cut_top: cutY + transform.cutY
357 | });
358 | this.data.watch.cut_top(null, this);
359 | }
360 | this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top;
361 | this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left;
362 | //图像边缘检测,防止截取到空白
363 | this._imgMarginDetectionScale();
364 | //停止居中裁剪框,继续修改图片位置
365 | this._moveDuring();
366 | this.setData({
367 | scale: this.data.scale,
368 | _img_top: this.data._img_top,
369 | _img_left: this.data._img_left
370 | });
371 | !this.data._canvas_overflow && this._draw();
372 | //可以居中裁剪框了
373 | this._moveStop(); //结束操作
374 | },
375 | /**
376 | * 设置剪裁框位置
377 | */
378 | setCutXY(x, y) {
379 | this.setData({
380 | cut_top: y,
381 | cut_left: x
382 | });
383 | },
384 | /**
385 | * 设置剪裁框尺寸
386 | */
387 | setCutSize(w, h) {
388 | this.setData({
389 | width: w,
390 | height: h
391 | });
392 | this._computeCutSize();
393 | },
394 | /**
395 | * 设置剪裁框和图片居中
396 | */
397 | setCutCenter() {
398 | let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
399 | let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
400 | //顺序不能变
401 | this.setData({
402 | _img_top: this.data._img_top - this.data.cut_top + cut_top,
403 | cut_top: cut_top, //截取的框上边距
404 | _img_left: this.data._img_left - this.data.cut_left + cut_left,
405 | cut_left: cut_left, //截取的框左边距
406 | });
407 | },
408 | _setCutCenter() {
409 | let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
410 | let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
411 | this.setData({
412 | cut_top: cut_top, //截取的框上边距
413 | cut_left: cut_left, //截取的框左边距
414 | });
415 | },
416 | /**
417 | * 设置剪裁框宽度-即将废弃
418 | */
419 | setWidth(width) {
420 | this.setData({
421 | width: width
422 | });
423 | this._computeCutSize();
424 | },
425 | /**
426 | * 设置剪裁框高度-即将废弃
427 | */
428 | setHeight(height) {
429 | this.setData({
430 | height: height
431 | });
432 | this._computeCutSize();
433 | },
434 | /**
435 | * 是否锁定旋转
436 | */
437 | setDisableRotate(value) {
438 | this.data.disable_rotate = value;
439 | },
440 | /**
441 | * 是否限制移动
442 | */
443 | setLimitMove(value) {
444 | this.setData({
445 | _cut_animation: true,
446 | limit_move: !!value
447 | });
448 | },
449 | /**
450 | * 初始化图片,包括位置、大小、旋转角度
451 | */
452 | imgReset() {
453 | this.setData({
454 | scale: 1,
455 | angle: 0,
456 | _img_top: wx.getSystemInfoSync().windowHeight / 2,
457 | _img_left: wx.getSystemInfoSync().windowWidth / 2,
458 | })
459 | },
460 | /**
461 | * 加载(更换)图片
462 | */
463 | pushImg(src) {
464 | if (src) {
465 | this.setData({
466 | imgSrc: src
467 | });
468 | //发现是手动赋值直接返回,交给watch处理
469 | return;
470 | }
471 |
472 | // getImageInfo接口传入 src: '' 会导致内存泄漏
473 |
474 | if (!this.data.imgSrc) return;
475 | wx.getImageInfo({
476 | src: this.data.imgSrc,
477 | success: (res) => {
478 | this.data.imageObject = res;
479 | //图片非本地路径需要换成本地路径
480 | if (this.data.imgSrc.search(/tmp/) == -1) {
481 | this.setData({
482 | imgSrc: res.path
483 | });
484 | }
485 | //计算最后图片尺寸
486 | this._imgComputeSize();
487 | if (this.data.limit_move) {
488 | //限制移动,不留空白处理
489 | this._imgMarginDetectionScale();
490 | }
491 | this._draw();
492 | },
493 | fail: (err) => {
494 | this.setData({
495 | imgSrc: ''
496 | });
497 | }
498 | });
499 | },
500 | imageLoad(e) {
501 | setTimeout(() => {
502 | this.triggerEvent('imageload', this.data.imageObject);
503 |
504 | })
505 | },
506 | /**
507 | * 设置图片放大缩小
508 | */
509 | setScale(scale) {
510 | if (!scale) return;
511 | this.setData({
512 | scale: scale
513 | });
514 | !this.data._canvas_overflow && this._draw();
515 | },
516 | /**
517 | * 设置图片旋转角度
518 | */
519 | setAngle(angle) {
520 | if (!angle) return;
521 | this.setData({
522 | _cut_animation: true,
523 | angle: angle
524 | });
525 | this._imgMarginDetectionScale();
526 | !this.data._canvas_overflow && this._draw();
527 | },
528 | _initCanvas() {
529 | //初始化canvas
530 | if (!this.data.ctx) {
531 | this.data.ctx = wx.createCanvasContext("image-cropper", this);
532 | }
533 | },
534 | /**
535 | * 根据开发者设置的图片目标尺寸计算实际尺寸
536 | */
537 | _initImageSize() {
538 | //处理宽高特殊单位 %>px
539 | if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) {
540 | let width = this.data.INIT_IMGWIDTH.replace("%", "");
541 | this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width;
542 | }
543 | if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) {
544 | let height = this.data.img_height.replace("%", "");
545 | this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height;
546 | }
547 | },
548 | /**
549 | * 检测剪裁框位置是否在允许的范围内(屏幕内)
550 | */
551 | _cutDetectionPosition() {
552 | let _cutDetectionPositionTop = () => {
553 | //检测上边距是否在范围内
554 | if (this.data.cut_top < 0) {
555 | this.setData({
556 | cut_top: 0
557 | });
558 | }
559 | if (this.data.cut_top > this.data.info.windowHeight - this.data.height) {
560 | this.setData({
561 | cut_top: this.data.info.windowHeight - this.data.height
562 | });
563 | }
564 | },
565 | _cutDetectionPositionLeft = () => {
566 | //检测左边距是否在范围内
567 | if (this.data.cut_left < 0) {
568 | this.setData({
569 | cut_left: 0
570 | });
571 | }
572 | if (this.data.cut_left > this.data.info.windowWidth - this.data.width) {
573 | this.setData({
574 | cut_left: this.data.info.windowWidth - this.data.width
575 | });
576 | }
577 | };
578 | //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
579 | if (this.data.cut_top == null && this.data.cut_left == null) {
580 | this._setCutCenter();
581 | } else if (this.data.cut_top != null && this.data.cut_left != null) {
582 | _cutDetectionPositionTop();
583 | _cutDetectionPositionLeft();
584 | } else if (this.data.cut_top != null && this.data.cut_left == null) {
585 | _cutDetectionPositionTop();
586 | this.setData({
587 | cut_left: (this.data.info.windowWidth - this.data.width) / 2
588 | });
589 | } else if (this.data.cut_top == null && this.data.cut_left != null) {
590 | _cutDetectionPositionLeft();
591 | this.setData({
592 | cut_top: (this.data.info.windowHeight - this.data.height) / 2
593 | });
594 | }
595 | },
596 | /**
597 | * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染
598 | * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外
599 | */
600 | _canvasDetectionPosition() {
601 | if (this.data.canvas_top == null && this.data.canvas_left == null) {
602 | this.data._canvas_overflow = false;
603 | this.setData({
604 | canvas_top: -5000,
605 | canvas_left: -5000
606 | });
607 | } else if (this.data.canvas_top != null && this.data.canvas_left != null) {
608 | if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) {
609 | this.data._canvas_overflow = true;
610 | } else {
611 | this.data._canvas_overflow = false;
612 | }
613 | } else if (this.data.canvas_top != null && this.data.canvas_left == null) {
614 | this.setData({
615 | canvas_left: 0
616 | });
617 | } else if (this.data.canvas_top == null && this.data.canvas_left != null) {
618 | this.setData({
619 | canvas_top: 0
620 | });
621 | if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) {
622 | this.data._canvas_overflow = true;
623 | } else {
624 | this.data._canvas_overflow = false;
625 | }
626 | }
627 | },
628 | /**
629 | * 图片边缘检测-位置
630 | */
631 | _imgMarginDetectionPosition(scale) {
632 | if (!this.data.limit_move) return;
633 | let left = this.data._img_left;
634 | let top = this.data._img_top;
635 | var scale = scale || this.data.scale;
636 | let img_width = this.data.img_width;
637 | let img_height = this.data.img_height;
638 | if (this.data.angle / 90 % 2) {
639 | img_width = this.data.img_height;
640 | img_height = this.data.img_width;
641 | }
642 | left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2;
643 | left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2;
644 | top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2;
645 | top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2;
646 | this.setData({
647 | _img_left: left,
648 | _img_top: top,
649 | scale: scale
650 | })
651 | },
652 | /**
653 | * 图片边缘检测-缩放
654 | */
655 | _imgMarginDetectionScale() {
656 | if (!this.data.limit_move) return;
657 | let scale = this.data.scale;
658 | let img_width = this.data.img_width;
659 | let img_height = this.data.img_height;
660 | if (this.data.angle / 90 % 2) {
661 | img_width = this.data.img_height;
662 | img_height = this.data.img_width;
663 | }
664 | if (img_width * scale < this.data.width) {
665 | scale = this.data.width / img_width;
666 | }
667 | if (img_height * scale < this.data.height) {
668 | scale = Math.max(scale, this.data.height / img_height);
669 | }
670 | this._imgMarginDetectionPosition(scale);
671 | },
672 | _setData(obj) {
673 | let data = {};
674 | for (var key in obj) {
675 | if (this.data[key] != obj[key]) {
676 | data[key] = obj[key];
677 | }
678 | }
679 | this.setData(data);
680 | return data;
681 | },
682 | /**
683 | * 计算图片尺寸
684 | */
685 | _imgComputeSize() {
686 | let img_width = this.data.img_width,
687 | img_height = this.data.img_height;
688 | if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
689 | //默认按图片最小边 = 对应裁剪框尺寸
690 | img_width = this.data.imageObject.width;
691 | img_height = this.data.imageObject.height;
692 | if (img_width / img_height > this.data.width / this.data.height) {
693 | img_height = this.data.height;
694 | img_width = this.data.imageObject.width / this.data.imageObject.height * img_height;
695 | } else {
696 | img_width = this.data.width;
697 | img_height = this.data.imageObject.height / this.data.imageObject.width * img_width;
698 | }
699 | } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
700 | img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT;
701 | } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) {
702 | img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH;
703 | }
704 | this.setData({
705 | img_width: img_width,
706 | img_height: img_height
707 | });
708 | },
709 | //改变截取框大小
710 | _computeCutSize() {
711 | if (this.data.width > this.data.info.windowWidth) {
712 | this.setData({
713 | width: this.data.info.windowWidth,
714 | });
715 | } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) {
716 | this.setData({
717 | cut_left: this.data.info.windowWidth - this.data.cut_left,
718 | });
719 | };
720 | if (this.data.height > this.data.info.windowHeight) {
721 | this.setData({
722 | height: this.data.info.windowHeight,
723 | });
724 | } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) {
725 | this.setData({
726 | cut_top: this.data.info.windowHeight - this.data.cut_top,
727 | });
728 | }!this.data._canvas_overflow && this._draw();
729 | },
730 | //开始触摸
731 | _start(event) {
732 | this.data._flag_img_endtouch = false;
733 | if (event.touches.length == 1) {
734 | //单指拖动
735 | this.data._touch_img_relative[0] = {
736 | x: (event.touches[0].clientX - this.data._img_left),
737 | y: (event.touches[0].clientY - this.data._img_top)
738 | }
739 | } else {
740 | //双指放大
741 | let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX);
742 | let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY);
743 | this.data._touch_img_relative = [{
744 | x: (event.touches[0].clientX - this.data._img_left),
745 | y: (event.touches[0].clientY - this.data._img_top)
746 | }, {
747 | x: (event.touches[1].clientX - this.data._img_left),
748 | y: (event.touches[1].clientY - this.data._img_top)
749 | }];
750 | this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
751 | }!this.data._canvas_overflow && this._draw();
752 | },
753 | _move_throttle() {
754 | //安卓需要节流
755 | if (this.data.info.platform == 'android') {
756 | clearTimeout(this.data.MOVE_THROTTLE);
757 | this.data.MOVE_THROTTLE = setTimeout(() => {
758 | this.data.MOVE_THROTTLE_FLAG = true;
759 | }, 1000 / 40)
760 | return this.data.MOVE_THROTTLE_FLAG;
761 | } else {
762 | this.data.MOVE_THROTTLE_FLAG = true;
763 | }
764 | },
765 | _move(event) {
766 | if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return;
767 | this.data.MOVE_THROTTLE_FLAG = false;
768 | this._move_throttle();
769 | this._moveDuring();
770 | if (event.touches.length == 1) {
771 | //单指拖动
772 | let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x),
773 | top = (event.touches[0].clientY - this.data._touch_img_relative[0].y);
774 | //图像边缘检测,防止截取到空白
775 | this.data._img_left = left;
776 | this.data._img_top = top;
777 | this._imgMarginDetectionPosition();
778 | this.setData({
779 | _img_left: this.data._img_left,
780 | _img_top: this.data._img_top
781 | });
782 | } else {
783 | //双指放大
784 | let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)),
785 | height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)),
786 | hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
787 | scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length),
788 | current_deg = 0;
789 | scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
790 | scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
791 | //图像边缘检测,防止截取到空白
792 | this.data.scale = scale;
793 | this._imgMarginDetectionScale();
794 | //双指旋转(如果没禁用旋转)
795 | let _touch_img_relative = [{
796 | x: (event.touches[0].clientX - this.data._img_left),
797 | y: (event.touches[0].clientY - this.data._img_top)
798 | }, {
799 | x: (event.touches[1].clientX - this.data._img_left),
800 | y: (event.touches[1].clientY - this.data._img_top)
801 | }];
802 | if (!this.data.disable_rotate) {
803 | let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x);
804 | let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x);
805 | let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x);
806 | let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x);
807 | //当前旋转的角度
808 | let first_deg = first_atan - first_atan_old,
809 | second_deg = second_atan - second_atan_old;
810 | if (first_deg != 0) {
811 | current_deg = first_deg;
812 | } else if (second_deg != 0) {
813 | current_deg = second_deg;
814 | }
815 | }
816 | this.data._touch_img_relative = _touch_img_relative;
817 | this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
818 | //更新视图
819 | this.setData({
820 | angle: this.data.angle + current_deg,
821 | scale: this.data.scale
822 | });
823 | }!this.data._canvas_overflow && this._draw();
824 | },
825 | //结束操作
826 | _end(event) {
827 | this.data._flag_img_endtouch = true;
828 | this._moveStop();
829 | },
830 | //点击中间剪裁框处理
831 | _click(event) {
832 | if (!this.data.imgSrc) {
833 | //调起上传
834 | this.upload();
835 | return;
836 | }
837 | this._draw(() => {
838 | let x = event.detail ? event.detail.x : event.touches[0].clientX;
839 | let y = event.detail ? event.detail.y : event.touches[0].clientY;
840 | if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) {
841 | //生成图片并回调
842 | wx.canvasToTempFilePath({
843 | width: this.data.width * this.data.export_scale,
844 | height: Math.round(this.data.height * this.data.export_scale),
845 | destWidth: this.data.width * this.data.export_scale,
846 | destHeight: Math.round(this.data.height) * this.data.export_scale,
847 | fileType: 'png',
848 | quality: this.data.quality,
849 | canvasId: this.data.el,
850 | success: (res) => {
851 | this.triggerEvent('tapcut', {
852 | url: res.tempFilePath,
853 | width: this.data.width * this.data.export_scale,
854 | height: this.data.height * this.data.export_scale
855 | });
856 | }
857 | }, this)
858 | }
859 | });
860 | },
861 | //渲染
862 | _draw(callback) {
863 | if (!this.data.imgSrc) return;
864 | let draw = () => {
865 | //图片实际大小
866 | let img_width = this.data.img_width * this.data.scale * this.data.export_scale;
867 | let img_height = this.data.img_height * this.data.scale * this.data.export_scale;
868 | //canvas和图片的相对距离
869 | var xpos = this.data._img_left - this.data.cut_left;
870 | var ypos = this.data._img_top - this.data.cut_top;
871 | //旋转画布
872 | this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale);
873 | this.data.ctx.rotate(this.data.angle * Math.PI / 180);
874 | this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height);
875 | this.data.ctx.draw(false, () => {
876 | callback && callback();
877 | });
878 | }
879 | if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) {
880 | //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方
881 | this.setData({
882 | _canvas_height: this.data.height,
883 | _canvas_width: this.data.width,
884 | }, () => {
885 | //延迟40毫秒防止点击过快出现拉伸或裁剪过多
886 | setTimeout(() => {
887 | draw();
888 | }, 40);
889 | });
890 | } else {
891 | draw();
892 | }
893 | },
894 | //裁剪框处理
895 | _cutTouchMove(e) {
896 | if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) {
897 | if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return;
898 | //节流
899 | this.data.MOVE_THROTTLE_FLAG = false;
900 | this._move_throttle();
901 | let width = this.data.width,
902 | height = this.data.height,
903 | cut_top = this.data.cut_top,
904 | cut_left = this.data.cut_left,
905 | size_correct = () => {
906 | width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width;
907 | height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height;
908 | },
909 | size_inspect = () => {
910 | if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) {
911 | size_correct();
912 | return false;
913 | } else {
914 | size_correct();
915 | return true;
916 | }
917 | };
918 | height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY));
919 | switch (this.data.CUT_START.corner) {
920 | case 1:
921 | width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
922 | if (this.data.disable_ratio) {
923 | height = width / (this.data.width / this.data.height)
924 | }
925 | if (!size_inspect()) return;
926 | cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width);
927 | break
928 | case 2:
929 | width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
930 | if (this.data.disable_ratio) {
931 | height = width / (this.data.width / this.data.height)
932 | }
933 | if (!size_inspect()) return;
934 | cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height)
935 | cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width)
936 | break
937 | case 3:
938 | width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
939 | if (this.data.disable_ratio) {
940 | height = width / (this.data.width / this.data.height)
941 | }
942 | if (!size_inspect()) return;
943 | cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height);
944 | break
945 | case 4:
946 | width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
947 | if (this.data.disable_ratio) {
948 | height = width / (this.data.width / this.data.height)
949 | }
950 | if (!size_inspect()) return;
951 | break
952 | }
953 | if (!this.data.disable_width && !this.data.disable_height) {
954 | this.setData({
955 | width: width,
956 | cut_left: cut_left,
957 | height: height,
958 | cut_top: cut_top,
959 | })
960 | } else if (!this.data.disable_width) {
961 | this.setData({
962 | width: width,
963 | cut_left: cut_left
964 | })
965 | } else if (!this.data.disable_height) {
966 | this.setData({
967 | height: height,
968 | cut_top: cut_top
969 | })
970 | }
971 | this._imgMarginDetectionScale();
972 | }
973 | },
974 | _cutTouchStart(e) {
975 | let currentX = e.touches[0].clientX;
976 | let currentY = e.touches[0].clientY;
977 | let cutbox_top4 = this.data.cut_top + this.data.height - 30;
978 | let cutbox_bottom4 = this.data.cut_top + this.data.height + 20;
979 | let cutbox_left4 = this.data.cut_left + this.data.width - 30;
980 | let cutbox_right4 = this.data.cut_left + this.data.width + 30;
981 |
982 | let cutbox_top3 = this.data.cut_top - 30;
983 | let cutbox_bottom3 = this.data.cut_top + 30;
984 | let cutbox_left3 = this.data.cut_left + this.data.width - 30;
985 | let cutbox_right3 = this.data.cut_left + this.data.width + 30;
986 |
987 | let cutbox_top2 = this.data.cut_top - 30;
988 | let cutbox_bottom2 = this.data.cut_top + 30;
989 | let cutbox_left2 = this.data.cut_left - 30;
990 | let cutbox_right2 = this.data.cut_left + 30;
991 |
992 | let cutbox_top1 = this.data.cut_top + this.data.height - 30;
993 | let cutbox_bottom1 = this.data.cut_top + this.data.height + 30;
994 | let cutbox_left1 = this.data.cut_left - 30;
995 | let cutbox_right1 = this.data.cut_left + 30;
996 | if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) {
997 | this._moveDuring();
998 | this.data._flag_cut_touch = true;
999 | this.data._flag_img_endtouch = true;
1000 | this.data.CUT_START = {
1001 | width: this.data.width,
1002 | height: this.data.height,
1003 | x: currentX,
1004 | y: currentY,
1005 | corner: 4
1006 | }
1007 | } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) {
1008 | this._moveDuring();
1009 | this.data._flag_cut_touch = true;
1010 | this.data._flag_img_endtouch = true;
1011 | this.data.CUT_START = {
1012 | width: this.data.width,
1013 | height: this.data.height,
1014 | x: currentX,
1015 | y: currentY,
1016 | cut_top: this.data.cut_top,
1017 | cut_left: this.data.cut_left,
1018 | corner: 3
1019 | }
1020 | } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) {
1021 | this._moveDuring();
1022 | this.data._flag_cut_touch = true;
1023 | this.data._flag_img_endtouch = true;
1024 | this.data.CUT_START = {
1025 | width: this.data.width,
1026 | height: this.data.height,
1027 | cut_top: this.data.cut_top,
1028 | cut_left: this.data.cut_left,
1029 | x: currentX,
1030 | y: currentY,
1031 | corner: 2
1032 | }
1033 | } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) {
1034 | this._moveDuring();
1035 | this.data._flag_cut_touch = true;
1036 | this.data._flag_img_endtouch = true;
1037 | this.data.CUT_START = {
1038 | width: this.data.width,
1039 | height: this.data.height,
1040 | cut_top: this.data.cut_top,
1041 | cut_left: this.data.cut_left,
1042 | x: currentX,
1043 | y: currentY,
1044 | corner: 1
1045 | }
1046 | }
1047 | },
1048 | _cutTouchEnd(e) {
1049 | this._moveStop();
1050 | this.data._flag_cut_touch = false;
1051 | },
1052 | //停止移动时需要做的操作
1053 | _moveStop() {
1054 | //清空之前的自动居中延迟函数并添加最新的
1055 | clearTimeout(this.data.TIME_CUT_CENTER);
1056 | this.data.TIME_CUT_CENTER = setTimeout(() => {
1057 | //动画启动
1058 | if (!this.data._cut_animation) {
1059 | this.setData({
1060 | _cut_animation: true
1061 | });
1062 | }
1063 | this.setCutCenter();
1064 | }, 1000)
1065 | //清空之前的背景变化延迟函数并添加最新的
1066 | clearTimeout(this.data.TIME_BG);
1067 | this.data.TIME_BG = setTimeout(() => {
1068 | if (this.data._flag_bright) {
1069 | this.setData({
1070 | _flag_bright: false
1071 | });
1072 | }
1073 | }, 2000)
1074 | },
1075 | //移动中
1076 | _moveDuring() {
1077 | //清空之前的自动居中延迟函数
1078 | clearTimeout(this.data.TIME_CUT_CENTER);
1079 | //清空之前的背景变化延迟函数
1080 | clearTimeout(this.data.TIME_BG);
1081 | //高亮背景
1082 | if (!this.data._flag_bright) {
1083 | this.setData({
1084 | _flag_bright: true
1085 | });
1086 | }
1087 | },
1088 | //监听器
1089 | _watcher() {
1090 | Object.keys(this.data).forEach(v => {
1091 | this._observe(this.data, v, this.data.watch[v]);
1092 | })
1093 | },
1094 | _observe(obj, key, watchFun) {
1095 | var val = obj[key];
1096 | Object.defineProperty(obj, key, {
1097 | configurable: true,
1098 | enumerable: true,
1099 | set: (value) => {
1100 | val = value;
1101 | watchFun && watchFun(val, this);
1102 | },
1103 | get() {
1104 | if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) {
1105 | let ret = parseFloat(parseFloat(val).toFixed(3));
1106 | if (typeof val == "string" && val.indexOf("%") != -1) {
1107 | ret += '%';
1108 | }
1109 | return ret;
1110 | }
1111 | return val;
1112 | }
1113 | })
1114 | },
1115 | _preventTouchMove() {}
1116 | }
1117 | })
--------------------------------------------------------------------------------
/demo/component/image-cropper/image-cropper.js:
--------------------------------------------------------------------------------
1 | Component({
2 | properties: {
3 | /**
4 | * 图片路径
5 | */
6 | 'imgSrc': {
7 | type: String
8 | },
9 | /**
10 | * 裁剪框高度
11 | */
12 | 'height': {
13 | type: Number,
14 | value: 200
15 | },
16 | /**
17 | * 裁剪框宽度
18 | */
19 | 'width': {
20 | type: Number,
21 | value: 200
22 | },
23 | /**
24 | * 裁剪框最小尺寸
25 | */
26 | 'min_width': {
27 | type: Number,
28 | value: 100
29 | },
30 | 'min_height': {
31 | type: Number,
32 | value: 100
33 | },
34 | /**
35 | * 裁剪框最大尺寸
36 | */
37 | 'max_width': {
38 | type: Number,
39 | value: 300
40 | },
41 | 'max_height': {
42 | type: Number,
43 | value: 300
44 | },
45 | /**
46 | * 裁剪框禁止拖动
47 | */
48 | 'disable_width': {
49 | type: Boolean,
50 | value: false
51 | },
52 | 'disable_height': {
53 | type: Boolean,
54 | value: false
55 | },
56 | /**
57 | * 锁定裁剪框比例
58 | */
59 | 'disable_ratio': {
60 | type: Boolean,
61 | value: false
62 | },
63 | /**
64 | * 生成的图片尺寸相对剪裁框的比例
65 | */
66 | 'export_scale': {
67 | type: Number,
68 | value: 3
69 | },
70 | /**
71 | * 生成的图片质量0-1
72 | */
73 | 'quality': {
74 | type: Number,
75 | value: 1
76 | },
77 | 'cut_top': {
78 | type: Number,
79 | value: null
80 | },
81 | 'cut_left': {
82 | type: Number,
83 | value: null
84 | },
85 | /**
86 | * canvas上边距(不设置默认不显示)
87 | */
88 | 'canvas_top': {
89 | type: Number,
90 | value: null
91 | },
92 | /**
93 | * canvas左边距(不设置默认不显示)
94 | */
95 | 'canvas_left': {
96 | type: Number,
97 | value: null
98 | },
99 | /**
100 | * 图片宽度
101 | */
102 | 'img_width': {
103 | type: null,
104 | value: null
105 | },
106 | /**
107 | * 图片高度
108 | */
109 | 'img_height': {
110 | type: null,
111 | value: null
112 | },
113 | /**
114 | * 图片缩放比
115 | */
116 | 'scale': {
117 | type: Number,
118 | value: 1
119 | },
120 | /**
121 | * 图片旋转角度
122 | */
123 | 'angle': {
124 | type: Number,
125 | value: 0
126 | },
127 | /**
128 | * 最小缩放比
129 | */
130 | 'min_scale': {
131 | type: Number,
132 | value: 0.5
133 | },
134 | /**
135 | * 最大缩放比
136 | */
137 | 'max_scale': {
138 | type: Number,
139 | value: 2
140 | },
141 | /**
142 | * 是否禁用旋转
143 | */
144 | 'disable_rotate': {
145 | type: Boolean,
146 | value: false
147 | },
148 | /**
149 | * 是否限制移动范围(剪裁框只能在图片内)
150 | */
151 | 'limit_move': {
152 | type: Boolean,
153 | value: false
154 | }
155 | },
156 | data: {
157 | el: 'image-cropper', //暂时无用
158 | info: wx.getSystemInfoSync(),
159 | MOVE_THROTTLE: null, //触摸移动节流settimeout
160 | MOVE_THROTTLE_FLAG: true, //节流标识
161 | INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
162 | INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸)
163 | TIME_BG: null, //背景变暗延时函数
164 | TIME_CUT_CENTER: null,
165 | _touch_img_relative: [{
166 | x: 0,
167 | y: 0
168 | }], //鼠标和图片中心的相对位置
169 | _flag_cut_touch: false, //是否是拖动裁剪框
170 | _hypotenuse_length: 0, //双指触摸时斜边长度
171 | _flag_img_endtouch: false, //是否结束触摸
172 | _flag_bright: true, //背景是否亮
173 | _canvas_overflow: true, //canvas缩略图是否在屏幕外面
174 | _canvas_width: 200,
175 | _canvas_height: 200,
176 | origin_x: 0.5, //图片旋转中心
177 | origin_y: 0.5, //图片旋转中心
178 | _cut_animation: false, //是否开启图片和裁剪框过渡
179 | _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距
180 | _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距
181 | watch: {
182 | //监听截取框宽高变化
183 | width(value, that) {
184 | if (value < that.data.min_width) {
185 | that.setData({
186 | width: that.data.min_width
187 | });
188 | }
189 | that._computeCutSize();
190 | },
191 | height(value, that) {
192 | if (value < that.data.min_height) {
193 | that.setData({
194 | height: that.data.min_height
195 | });
196 | }
197 | that._computeCutSize();
198 | },
199 | angle(value, that) {
200 | //停止居中裁剪框,继续修改图片位置
201 | that._moveStop();
202 | if (that.data.limit_move) {
203 | if (that.data.angle % 90) {
204 | that.setData({
205 | angle: Math.round(that.data.angle / 90) * 90
206 | });
207 | return;
208 | }
209 | }
210 | },
211 | _cut_animation(value, that) {
212 | //开启过渡300毫秒之后自动关闭
213 | clearTimeout(that.data._cut_animation_time);
214 | if (value) {
215 | that.data._cut_animation_time = setTimeout(() => {
216 | that.setData({
217 | _cut_animation: false
218 | });
219 | }, 300)
220 | }
221 | },
222 | limit_move(value, that) {
223 | if (value) {
224 | if (that.data.angle % 90) {
225 | that.setData({
226 | angle: Math.round(that.data.angle / 90) * 90
227 | });
228 | }
229 | that._imgMarginDetectionScale();
230 | !that.data._canvas_overflow && that._draw();
231 | }
232 | },
233 | canvas_top(value, that) {
234 | that._canvasDetectionPosition();
235 | },
236 | canvas_left(value, that) {
237 | that._canvasDetectionPosition();
238 | },
239 | imgSrc(value, that) {
240 | that.pushImg();
241 | },
242 | cut_top(value, that) {
243 | that._cutDetectionPosition();
244 | if (that.data.limit_move) {
245 | !that.data._canvas_overflow && that._draw();
246 | }
247 | },
248 | cut_left(value, that) {
249 | that._cutDetectionPosition();
250 | if (that.data.limit_move) {
251 | !that.data._canvas_overflow && that._draw();
252 | }
253 | }
254 | }
255 | },
256 | attached() {
257 | this.data.info = wx.getSystemInfoSync();
258 | //启用数据监听
259 | this._watcher();
260 | this.data.INIT_IMGWIDTH = this.data.img_width;
261 | this.data.INIT_IMGHEIGHT = this.data.img_height;
262 | this.setData({
263 | _canvas_height: this.data.height,
264 | _canvas_width: this.data.width,
265 | });
266 | this._initCanvas();
267 | this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc);
268 | //根据开发者设置的图片目标尺寸计算实际尺寸
269 | this._initImageSize();
270 | //设置裁剪框大小>设置图片尺寸>绘制canvas
271 | this._computeCutSize();
272 | //检查裁剪框是否在范围内
273 | this._cutDetectionPosition();
274 | //检查canvas是否在范围内
275 | this._canvasDetectionPosition();
276 | //初始化完成
277 | this.triggerEvent('load', {
278 | cropper: this
279 | });
280 | },
281 | methods: {
282 | /**
283 | * 上传图片
284 | */
285 | upload() {
286 | let that = this;
287 | wx.chooseImage({
288 | count: 1,
289 | sizeType: ['original', 'compressed'],
290 | sourceType: ['album', 'camera'],
291 | success(res) {
292 | const tempFilePaths = res.tempFilePaths[0];
293 | that.pushImg(tempFilePaths);
294 | wx.showLoading({
295 | title: '加载中...'
296 | })
297 | }
298 | })
299 | },
300 | /**
301 | * 返回图片信息
302 | */
303 | getImg(getCallback) {
304 | this._draw(() => {
305 | wx.canvasToTempFilePath({
306 | width: this.data.width * this.data.export_scale,
307 | height: Math.round(this.data.height * this.data.export_scale),
308 | destWidth: this.data.width * this.data.export_scale,
309 | destHeight: Math.round(this.data.height) * this.data.export_scale,
310 | fileType: 'png',
311 | quality: this.data.quality,
312 | canvasId: this.data.el,
313 | success: (res) => {
314 | getCallback({
315 | url: res.tempFilePath,
316 | width: this.data.width * this.data.export_scale,
317 | height: this.data.height * this.data.export_scale
318 | });
319 | }
320 | }, this)
321 | });
322 | },
323 | /**
324 | * 设置图片动画
325 | * {
326 | * x:10,//图片在原有基础上向下移动10px
327 | * y:10,//图片在原有基础上向右移动10px
328 | * angle:10,//图片在原有基础上旋转10deg
329 | * scale:0.5,//图片在原有基础上增加0.5倍
330 | * }
331 | */
332 | setTransform(transform) {
333 | if (!transform) return;
334 | if (!this.data.disable_rotate) {
335 | this.setData({
336 | angle: transform.angle ? this.data.angle + transform.angle : this.data.angle
337 | });
338 | }
339 | var scale = this.data.scale;
340 | if (transform.scale) {
341 | scale = this.data.scale + transform.scale;
342 | scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
343 | scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
344 | }
345 | this.data.scale = scale;
346 | let cutX = this.data.cut_left;
347 | let cutY = this.data.cut_top;
348 | if (transform.cutX) {
349 | this.setData({
350 | cut_left: cutX + transform.cutX
351 | });
352 | this.data.watch.cut_left(null, this);
353 | }
354 | if (transform.cutY) {
355 | this.setData({
356 | cut_top: cutY + transform.cutY
357 | });
358 | this.data.watch.cut_top(null, this);
359 | }
360 | this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top;
361 | this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left;
362 | //图像边缘检测,防止截取到空白
363 | this._imgMarginDetectionScale();
364 | //停止居中裁剪框,继续修改图片位置
365 | this._moveDuring();
366 | this.setData({
367 | scale: this.data.scale,
368 | _img_top: this.data._img_top,
369 | _img_left: this.data._img_left
370 | });
371 | !this.data._canvas_overflow && this._draw();
372 | //可以居中裁剪框了
373 | this._moveStop(); //结束操作
374 | },
375 | /**
376 | * 设置剪裁框位置
377 | */
378 | setCutXY(x, y) {
379 | this.setData({
380 | cut_top: y,
381 | cut_left: x
382 | });
383 | },
384 | /**
385 | * 设置剪裁框尺寸
386 | */
387 | setCutSize(w, h) {
388 | this.setData({
389 | width: w,
390 | height: h
391 | });
392 | this._computeCutSize();
393 | },
394 | /**
395 | * 设置剪裁框和图片居中
396 | */
397 | setCutCenter() {
398 | let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
399 | let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
400 | //顺序不能变
401 | this.setData({
402 | _img_top: this.data._img_top - this.data.cut_top + cut_top,
403 | cut_top: cut_top, //截取的框上边距
404 | _img_left: this.data._img_left - this.data.cut_left + cut_left,
405 | cut_left: cut_left, //截取的框左边距
406 | });
407 | },
408 | _setCutCenter() {
409 | let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5;
410 | let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5;
411 | this.setData({
412 | cut_top: cut_top, //截取的框上边距
413 | cut_left: cut_left, //截取的框左边距
414 | });
415 | },
416 | /**
417 | * 设置剪裁框宽度-即将废弃
418 | */
419 | setWidth(width) {
420 | this.setData({
421 | width: width
422 | });
423 | this._computeCutSize();
424 | },
425 | /**
426 | * 设置剪裁框高度-即将废弃
427 | */
428 | setHeight(height) {
429 | this.setData({
430 | height: height
431 | });
432 | this._computeCutSize();
433 | },
434 | /**
435 | * 是否锁定旋转
436 | */
437 | setDisableRotate(value) {
438 | this.data.disable_rotate = value;
439 | },
440 | /**
441 | * 是否限制移动
442 | */
443 | setLimitMove(value) {
444 | this.setData({
445 | _cut_animation: true,
446 | limit_move: !!value
447 | });
448 | },
449 | /**
450 | * 初始化图片,包括位置、大小、旋转角度
451 | */
452 | imgReset() {
453 | this.setData({
454 | scale: 1,
455 | angle: 0,
456 | _img_top: wx.getSystemInfoSync().windowHeight / 2,
457 | _img_left: wx.getSystemInfoSync().windowWidth / 2,
458 | })
459 | },
460 | /**
461 | * 加载(更换)图片
462 | */
463 | pushImg(src) {
464 | if (src) {
465 | this.setData({
466 | imgSrc: src
467 | });
468 | //发现是手动赋值直接返回,交给watch处理
469 | return;
470 | }
471 |
472 | // getImageInfo接口传入 src: '' 会导致内存泄漏
473 |
474 | if (!this.data.imgSrc) return;
475 | wx.getImageInfo({
476 | src: this.data.imgSrc,
477 | success: (res) => {
478 | this.data.imageObject = res;
479 | //图片非本地路径需要换成本地路径
480 | if (this.data.imgSrc.search(/tmp/) == -1) {
481 | this.setData({
482 | imgSrc: res.path
483 | });
484 | }
485 | //计算最后图片尺寸
486 | this._imgComputeSize();
487 | if (this.data.limit_move) {
488 | //限制移动,不留空白处理
489 | this._imgMarginDetectionScale();
490 | }
491 | this._draw();
492 | },
493 | fail: (err) => {
494 | this.setData({
495 | imgSrc: ''
496 | });
497 | }
498 | });
499 | },
500 | imageLoad(e) {
501 | setTimeout(() => {
502 | this.triggerEvent('imageload', this.data.imageObject);
503 |
504 | }, 1000)
505 | },
506 | /**
507 | * 设置图片放大缩小
508 | */
509 | setScale(scale) {
510 | if (!scale) return;
511 | this.setData({
512 | scale: scale
513 | });
514 | !this.data._canvas_overflow && this._draw();
515 | },
516 | /**
517 | * 设置图片旋转角度
518 | */
519 | setAngle(angle) {
520 | if (!angle) return;
521 | this.setData({
522 | _cut_animation: true,
523 | angle: angle
524 | });
525 | this._imgMarginDetectionScale();
526 | !this.data._canvas_overflow && this._draw();
527 | },
528 | _initCanvas() {
529 | //初始化canvas
530 | if (!this.data.ctx) {
531 | this.data.ctx = wx.createCanvasContext("image-cropper", this);
532 | }
533 | },
534 | /**
535 | * 根据开发者设置的图片目标尺寸计算实际尺寸
536 | */
537 | _initImageSize() {
538 | //处理宽高特殊单位 %>px
539 | if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) {
540 | let width = this.data.INIT_IMGWIDTH.replace("%", "");
541 | this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width;
542 | }
543 | if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) {
544 | let height = this.data.img_height.replace("%", "");
545 | this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height;
546 | }
547 | },
548 | /**
549 | * 检测剪裁框位置是否在允许的范围内(屏幕内)
550 | */
551 | _cutDetectionPosition() {
552 | let _cutDetectionPositionTop = () => {
553 | //检测上边距是否在范围内
554 | if (this.data.cut_top < 0) {
555 | this.setData({
556 | cut_top: 0
557 | });
558 | }
559 | if (this.data.cut_top > this.data.info.windowHeight - this.data.height) {
560 | this.setData({
561 | cut_top: this.data.info.windowHeight - this.data.height
562 | });
563 | }
564 | },
565 | _cutDetectionPositionLeft = () => {
566 | //检测左边距是否在范围内
567 | if (this.data.cut_left < 0) {
568 | this.setData({
569 | cut_left: 0
570 | });
571 | }
572 | if (this.data.cut_left > this.data.info.windowWidth - this.data.width) {
573 | this.setData({
574 | cut_left: this.data.info.windowWidth - this.data.width
575 | });
576 | }
577 | };
578 | //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
579 | if (this.data.cut_top == null && this.data.cut_left == null) {
580 | this._setCutCenter();
581 | } else if (this.data.cut_top != null && this.data.cut_left != null) {
582 | _cutDetectionPositionTop();
583 | _cutDetectionPositionLeft();
584 | } else if (this.data.cut_top != null && this.data.cut_left == null) {
585 | _cutDetectionPositionTop();
586 | this.setData({
587 | cut_left: (this.data.info.windowWidth - this.data.width) / 2
588 | });
589 | } else if (this.data.cut_top == null && this.data.cut_left != null) {
590 | _cutDetectionPositionLeft();
591 | this.setData({
592 | cut_top: (this.data.info.windowHeight - this.data.height) / 2
593 | });
594 | }
595 | },
596 | /**
597 | * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染
598 | * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外
599 | */
600 | _canvasDetectionPosition() {
601 | if (this.data.canvas_top == null && this.data.canvas_left == null) {
602 | this.data._canvas_overflow = false;
603 | this.setData({
604 | canvas_top: -5000,
605 | canvas_left: -5000
606 | });
607 | } else if (this.data.canvas_top != null && this.data.canvas_left != null) {
608 | if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) {
609 | this.data._canvas_overflow = true;
610 | } else {
611 | this.data._canvas_overflow = false;
612 | }
613 | } else if (this.data.canvas_top != null && this.data.canvas_left == null) {
614 | this.setData({
615 | canvas_left: 0
616 | });
617 | } else if (this.data.canvas_top == null && this.data.canvas_left != null) {
618 | this.setData({
619 | canvas_top: 0
620 | });
621 | if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) {
622 | this.data._canvas_overflow = true;
623 | } else {
624 | this.data._canvas_overflow = false;
625 | }
626 | }
627 | },
628 | /**
629 | * 图片边缘检测-位置
630 | */
631 | _imgMarginDetectionPosition(scale) {
632 | if (!this.data.limit_move) return;
633 | let left = this.data._img_left;
634 | let top = this.data._img_top;
635 | var scale = scale || this.data.scale;
636 | let img_width = this.data.img_width;
637 | let img_height = this.data.img_height;
638 | if (this.data.angle / 90 % 2) {
639 | img_width = this.data.img_height;
640 | img_height = this.data.img_width;
641 | }
642 | left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2;
643 | left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2;
644 | top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2;
645 | top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2;
646 | this.setData({
647 | _img_left: left,
648 | _img_top: top,
649 | scale: scale
650 | })
651 | },
652 | /**
653 | * 图片边缘检测-缩放
654 | */
655 | _imgMarginDetectionScale() {
656 | if (!this.data.limit_move) return;
657 | let scale = this.data.scale;
658 | let img_width = this.data.img_width;
659 | let img_height = this.data.img_height;
660 | if (this.data.angle / 90 % 2) {
661 | img_width = this.data.img_height;
662 | img_height = this.data.img_width;
663 | }
664 | if (img_width * scale < this.data.width) {
665 | scale = this.data.width / img_width;
666 | }
667 | if (img_height * scale < this.data.height) {
668 | scale = Math.max(scale, this.data.height / img_height);
669 | }
670 | this._imgMarginDetectionPosition(scale);
671 | },
672 | _setData(obj) {
673 | let data = {};
674 | for (var key in obj) {
675 | if (this.data[key] != obj[key]) {
676 | data[key] = obj[key];
677 | }
678 | }
679 | this.setData(data);
680 | return data;
681 | },
682 | /**
683 | * 计算图片尺寸
684 | */
685 | _imgComputeSize() {
686 | let img_width = this.data.img_width,
687 | img_height = this.data.img_height;
688 | if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
689 | //默认按图片最小边 = 对应裁剪框尺寸
690 | img_width = this.data.imageObject.width;
691 | img_height = this.data.imageObject.height;
692 | if (img_width / img_height > this.data.width / this.data.height) {
693 | img_height = this.data.height;
694 | img_width = this.data.imageObject.width / this.data.imageObject.height * img_height;
695 | } else {
696 | img_width = this.data.width;
697 | img_height = this.data.imageObject.height / this.data.imageObject.width * img_width;
698 | }
699 | } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) {
700 | img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT;
701 | } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) {
702 | img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH;
703 | }
704 | this.setData({
705 | img_width: img_width,
706 | img_height: img_height
707 | });
708 | },
709 | //改变截取框大小
710 | _computeCutSize() {
711 | if (this.data.width > this.data.info.windowWidth) {
712 | this.setData({
713 | width: this.data.info.windowWidth,
714 | });
715 | } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) {
716 | this.setData({
717 | cut_left: this.data.info.windowWidth - this.data.cut_left,
718 | });
719 | };
720 | if (this.data.height > this.data.info.windowHeight) {
721 | this.setData({
722 | height: this.data.info.windowHeight,
723 | });
724 | } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) {
725 | this.setData({
726 | cut_top: this.data.info.windowHeight - this.data.cut_top,
727 | });
728 | }!this.data._canvas_overflow && this._draw();
729 | },
730 | //开始触摸
731 | _start(event) {
732 | this.data._flag_img_endtouch = false;
733 | if (event.touches.length == 1) {
734 | //单指拖动
735 | this.data._touch_img_relative[0] = {
736 | x: (event.touches[0].clientX - this.data._img_left),
737 | y: (event.touches[0].clientY - this.data._img_top)
738 | }
739 | } else {
740 | //双指放大
741 | let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX);
742 | let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY);
743 | this.data._touch_img_relative = [{
744 | x: (event.touches[0].clientX - this.data._img_left),
745 | y: (event.touches[0].clientY - this.data._img_top)
746 | }, {
747 | x: (event.touches[1].clientX - this.data._img_left),
748 | y: (event.touches[1].clientY - this.data._img_top)
749 | }];
750 | this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
751 | }!this.data._canvas_overflow && this._draw();
752 | },
753 | _move_throttle() {
754 | //安卓需要节流
755 | if (this.data.info.platform == 'android') {
756 | clearTimeout(this.data.MOVE_THROTTLE);
757 | this.data.MOVE_THROTTLE = setTimeout(() => {
758 | this.data.MOVE_THROTTLE_FLAG = true;
759 | }, 1000 / 40)
760 | return this.data.MOVE_THROTTLE_FLAG;
761 | } else {
762 | this.data.MOVE_THROTTLE_FLAG = true;
763 | }
764 | },
765 | _move(event) {
766 | if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return;
767 | this.data.MOVE_THROTTLE_FLAG = false;
768 | this._move_throttle();
769 | this._moveDuring();
770 | if (event.touches.length == 1) {
771 | //单指拖动
772 | let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x),
773 | top = (event.touches[0].clientY - this.data._touch_img_relative[0].y);
774 | //图像边缘检测,防止截取到空白
775 | this.data._img_left = left;
776 | this.data._img_top = top;
777 | this._imgMarginDetectionPosition();
778 | this.setData({
779 | _img_left: this.data._img_left,
780 | _img_top: this.data._img_top
781 | });
782 | } else {
783 | //双指放大
784 | let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)),
785 | height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)),
786 | hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
787 | scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length),
788 | current_deg = 0;
789 | scale = scale <= this.data.min_scale ? this.data.min_scale : scale;
790 | scale = scale >= this.data.max_scale ? this.data.max_scale : scale;
791 | //图像边缘检测,防止截取到空白
792 | this.data.scale = scale;
793 | this._imgMarginDetectionScale();
794 | //双指旋转(如果没禁用旋转)
795 | let _touch_img_relative = [{
796 | x: (event.touches[0].clientX - this.data._img_left),
797 | y: (event.touches[0].clientY - this.data._img_top)
798 | }, {
799 | x: (event.touches[1].clientX - this.data._img_left),
800 | y: (event.touches[1].clientY - this.data._img_top)
801 | }];
802 | if (!this.data.disable_rotate) {
803 | let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x);
804 | let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x);
805 | let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x);
806 | let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x);
807 | //当前旋转的角度
808 | let first_deg = first_atan - first_atan_old,
809 | second_deg = second_atan - second_atan_old;
810 | if (first_deg != 0) {
811 | current_deg = first_deg;
812 | } else if (second_deg != 0) {
813 | current_deg = second_deg;
814 | }
815 | }
816 | this.data._touch_img_relative = _touch_img_relative;
817 | this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
818 | //更新视图
819 | this.setData({
820 | angle: this.data.angle + current_deg,
821 | scale: this.data.scale
822 | });
823 | }!this.data._canvas_overflow && this._draw();
824 | },
825 | //结束操作
826 | _end(event) {
827 | this.data._flag_img_endtouch = true;
828 | this._moveStop();
829 | },
830 | //点击中间剪裁框处理
831 | _click(event) {
832 | if (!this.data.imgSrc) {
833 | //调起上传
834 | this.upload();
835 | return;
836 | }
837 | this._draw(() => {
838 | let x = event.detail ? event.detail.x : event.touches[0].clientX;
839 | let y = event.detail ? event.detail.y : event.touches[0].clientY;
840 | if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) {
841 | //生成图片并回调
842 | wx.canvasToTempFilePath({
843 | width: this.data.width * this.data.export_scale,
844 | height: Math.round(this.data.height * this.data.export_scale),
845 | destWidth: this.data.width * this.data.export_scale,
846 | destHeight: Math.round(this.data.height) * this.data.export_scale,
847 | fileType: 'png',
848 | quality: this.data.quality,
849 | canvasId: this.data.el,
850 | success: (res) => {
851 | this.triggerEvent('tapcut', {
852 | url: res.tempFilePath,
853 | width: this.data.width * this.data.export_scale,
854 | height: this.data.height * this.data.export_scale
855 | });
856 | }
857 | }, this)
858 | }
859 | });
860 | },
861 | //渲染
862 | _draw(callback) {
863 | if (!this.data.imgSrc) return;
864 | let draw = () => {
865 | //图片实际大小
866 | let img_width = this.data.img_width * this.data.scale * this.data.export_scale;
867 | let img_height = this.data.img_height * this.data.scale * this.data.export_scale;
868 | //canvas和图片的相对距离
869 | var xpos = this.data._img_left - this.data.cut_left;
870 | var ypos = this.data._img_top - this.data.cut_top;
871 | //旋转画布
872 | this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale);
873 | this.data.ctx.rotate(this.data.angle * Math.PI / 180);
874 | this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height);
875 | this.data.ctx.draw(false, () => {
876 | callback && callback();
877 | });
878 | }
879 | if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) {
880 | //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方
881 | this.setData({
882 | _canvas_height: this.data.height,
883 | _canvas_width: this.data.width,
884 | }, () => {
885 | //延迟40毫秒防止点击过快出现拉伸或裁剪过多
886 | setTimeout(() => {
887 | draw();
888 | }, 40);
889 | });
890 | } else {
891 | draw();
892 | }
893 | },
894 | //裁剪框处理
895 | _cutTouchMove(e) {
896 | if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) {
897 | if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return;
898 | //节流
899 | this.data.MOVE_THROTTLE_FLAG = false;
900 | this._move_throttle();
901 | let width = this.data.width,
902 | height = this.data.height,
903 | cut_top = this.data.cut_top,
904 | cut_left = this.data.cut_left,
905 | size_correct = () => {
906 | width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width;
907 | height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height;
908 | },
909 | size_inspect = () => {
910 | if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) {
911 | size_correct();
912 | return false;
913 | } else {
914 | size_correct();
915 | return true;
916 | }
917 | };
918 | height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY));
919 | switch (this.data.CUT_START.corner) {
920 | case 1:
921 | width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
922 | if (this.data.disable_ratio) {
923 | height = width / (this.data.width / this.data.height)
924 | }
925 | if (!size_inspect()) return;
926 | cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width);
927 | break
928 | case 2:
929 | width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX;
930 | if (this.data.disable_ratio) {
931 | height = width / (this.data.width / this.data.height)
932 | }
933 | if (!size_inspect()) return;
934 | cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height)
935 | cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width)
936 | break
937 | case 3:
938 | width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
939 | if (this.data.disable_ratio) {
940 | height = width / (this.data.width / this.data.height)
941 | }
942 | if (!size_inspect()) return;
943 | cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height);
944 | break
945 | case 4:
946 | width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX;
947 | if (this.data.disable_ratio) {
948 | height = width / (this.data.width / this.data.height)
949 | }
950 | if (!size_inspect()) return;
951 | break
952 | }
953 | if (!this.data.disable_width && !this.data.disable_height) {
954 | this.setData({
955 | width: width,
956 | cut_left: cut_left,
957 | height: height,
958 | cut_top: cut_top,
959 | })
960 | } else if (!this.data.disable_width) {
961 | this.setData({
962 | width: width,
963 | cut_left: cut_left
964 | })
965 | } else if (!this.data.disable_height) {
966 | this.setData({
967 | height: height,
968 | cut_top: cut_top
969 | })
970 | }
971 | this._imgMarginDetectionScale();
972 | }
973 | },
974 | _cutTouchStart(e) {
975 | let currentX = e.touches[0].clientX;
976 | let currentY = e.touches[0].clientY;
977 | let cutbox_top4 = this.data.cut_top + this.data.height - 30;
978 | let cutbox_bottom4 = this.data.cut_top + this.data.height + 20;
979 | let cutbox_left4 = this.data.cut_left + this.data.width - 30;
980 | let cutbox_right4 = this.data.cut_left + this.data.width + 30;
981 |
982 | let cutbox_top3 = this.data.cut_top - 30;
983 | let cutbox_bottom3 = this.data.cut_top + 30;
984 | let cutbox_left3 = this.data.cut_left + this.data.width - 30;
985 | let cutbox_right3 = this.data.cut_left + this.data.width + 30;
986 |
987 | let cutbox_top2 = this.data.cut_top - 30;
988 | let cutbox_bottom2 = this.data.cut_top + 30;
989 | let cutbox_left2 = this.data.cut_left - 30;
990 | let cutbox_right2 = this.data.cut_left + 30;
991 |
992 | let cutbox_top1 = this.data.cut_top + this.data.height - 30;
993 | let cutbox_bottom1 = this.data.cut_top + this.data.height + 30;
994 | let cutbox_left1 = this.data.cut_left - 30;
995 | let cutbox_right1 = this.data.cut_left + 30;
996 | if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) {
997 | this._moveDuring();
998 | this.data._flag_cut_touch = true;
999 | this.data._flag_img_endtouch = true;
1000 | this.data.CUT_START = {
1001 | width: this.data.width,
1002 | height: this.data.height,
1003 | x: currentX,
1004 | y: currentY,
1005 | corner: 4
1006 | }
1007 | } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) {
1008 | this._moveDuring();
1009 | this.data._flag_cut_touch = true;
1010 | this.data._flag_img_endtouch = true;
1011 | this.data.CUT_START = {
1012 | width: this.data.width,
1013 | height: this.data.height,
1014 | x: currentX,
1015 | y: currentY,
1016 | cut_top: this.data.cut_top,
1017 | cut_left: this.data.cut_left,
1018 | corner: 3
1019 | }
1020 | } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) {
1021 | this._moveDuring();
1022 | this.data._flag_cut_touch = true;
1023 | this.data._flag_img_endtouch = true;
1024 | this.data.CUT_START = {
1025 | width: this.data.width,
1026 | height: this.data.height,
1027 | cut_top: this.data.cut_top,
1028 | cut_left: this.data.cut_left,
1029 | x: currentX,
1030 | y: currentY,
1031 | corner: 2
1032 | }
1033 | } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) {
1034 | this._moveDuring();
1035 | this.data._flag_cut_touch = true;
1036 | this.data._flag_img_endtouch = true;
1037 | this.data.CUT_START = {
1038 | width: this.data.width,
1039 | height: this.data.height,
1040 | cut_top: this.data.cut_top,
1041 | cut_left: this.data.cut_left,
1042 | x: currentX,
1043 | y: currentY,
1044 | corner: 1
1045 | }
1046 | }
1047 | },
1048 | _cutTouchEnd(e) {
1049 | this._moveStop();
1050 | this.data._flag_cut_touch = false;
1051 | },
1052 | //停止移动时需要做的操作
1053 | _moveStop() {
1054 | //清空之前的自动居中延迟函数并添加最新的
1055 | clearTimeout(this.data.TIME_CUT_CENTER);
1056 | this.data.TIME_CUT_CENTER = setTimeout(() => {
1057 | //动画启动
1058 | if (!this.data._cut_animation) {
1059 | this.setData({
1060 | _cut_animation: true
1061 | });
1062 | }
1063 | this.setCutCenter();
1064 | }, 1000)
1065 | //清空之前的背景变化延迟函数并添加最新的
1066 | clearTimeout(this.data.TIME_BG);
1067 | this.data.TIME_BG = setTimeout(() => {
1068 | if (this.data._flag_bright) {
1069 | this.setData({
1070 | _flag_bright: false
1071 | });
1072 | }
1073 | }, 2000)
1074 | },
1075 | //移动中
1076 | _moveDuring() {
1077 | //清空之前的自动居中延迟函数
1078 | clearTimeout(this.data.TIME_CUT_CENTER);
1079 | //清空之前的背景变化延迟函数
1080 | clearTimeout(this.data.TIME_BG);
1081 | //高亮背景
1082 | if (!this.data._flag_bright) {
1083 | this.setData({
1084 | _flag_bright: true
1085 | });
1086 | }
1087 | },
1088 | //监听器
1089 | _watcher() {
1090 | Object.keys(this.data).forEach(v => {
1091 | this._observe(this.data, v, this.data.watch[v]);
1092 | })
1093 | },
1094 | _observe(obj, key, watchFun) {
1095 | var val = obj[key];
1096 | Object.defineProperty(obj, key, {
1097 | configurable: true,
1098 | enumerable: true,
1099 | set: (value) => {
1100 | val = value;
1101 | watchFun && watchFun(val, this);
1102 | },
1103 | get() {
1104 | if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) {
1105 | let ret = parseFloat(parseFloat(val).toFixed(3));
1106 | if (typeof val == "string" && val.indexOf("%") != -1) {
1107 | ret += '%';
1108 | }
1109 | return ret;
1110 | }
1111 | return val;
1112 | }
1113 | })
1114 | },
1115 | _preventTouchMove() {}
1116 | }
1117 | })
--------------------------------------------------------------------------------