├── .gitignore
├── LICENSE
├── README.md
├── bower.json
├── dist
├── cropper.css
└── cropper.js
├── examples
├── demo.html
├── orientation_1.JPG
├── orientation_3.JPG
├── orientation_6.JPG
└── orientation_8.JPG
├── package.json
└── src
├── cropper.js
├── index.js
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | # dist/*.js
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # IDE
31 | .idea
32 | demo/**/*.bundle.js
33 |
34 | # Prototype File
35 | prototype
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 饿了么前端
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # image-cropper-touch
2 | A image cropper for mobile device.
3 |
4 | # requirements
5 |
6 | blueimp-load-image is required.
7 |
8 | ```Bash
9 | npm install blueimp-load-image
10 | ```
11 |
12 | ```HTML
13 |
14 | ```
15 |
16 | # Example
17 |
18 | View example in folder examples/demo.html.
19 |
20 | # License
21 | MIT
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-cropper-touch",
3 | "main": "dist/cropper.js",
4 | "version": "0.1.2",
5 | "authors": [
6 | "long.zhang "
7 | ],
8 | "license": "MIT",
9 | "ignore": [
10 | "demo",
11 | "test",
12 | "examples"
13 | ]
14 | }
--------------------------------------------------------------------------------
/dist/cropper.css:
--------------------------------------------------------------------------------
1 | .cropper {
2 | position: relative;
3 | overflow: hidden;
4 | }
5 |
6 | .cropper .crop-box {
7 | position: absolute;
8 | left: 50%;
9 | top: 50%;
10 | -webkit-transform: translate3d(-50%, -50%, 0);
11 | transform: translate3d(-50%, -50%, 0);
12 | box-sizing: border-box;
13 | border: 1px solid #ddd;
14 | z-index: 101;
15 | }
16 |
17 | .cropper .cover {
18 | position: absolute;
19 | background: rgba(0,0,0,0.5);
20 | width: 100%;
21 | z-index: 100;
22 | }
23 |
24 | .cropper .cover-start {
25 | top: 0;
26 | }
27 |
28 | .cropper .cover-end {
29 | bottom: 0;
30 | }
31 |
32 | .cropper-horizontal .cover {
33 | height: 100%;
34 | }
35 |
36 | .cropper-horizontal .cover-start {
37 | left: 0;
38 | }
39 |
40 | .cropper-horizontal .cover-end {
41 | right: 0;
42 | }
43 |
44 | .cropper img {
45 | max-width: none;
46 | }
--------------------------------------------------------------------------------
/dist/cropper.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1) {
53 | originalWidth = image.height;
54 | originalHeight = image.width;
55 | } else {
56 | originalWidth = image.width;
57 | originalHeight = image.height;
58 | }
59 |
60 | self.imageState.width = originalWidth;
61 | self.imageState.height = originalHeight;
62 |
63 | self.initScale();
64 |
65 | var minScale = self.scaleRange[0];
66 | var imageWidth = minScale * originalWidth;
67 | var imageHeight = minScale * originalHeight;
68 | selfImage.style.width = imageWidth + 'px';
69 | selfImage.style.height = imageHeight + 'px';
70 |
71 | var imageLeft, imageTop;
72 |
73 | var cropBoxRect = self.cropBoxRect;
74 |
75 | if (originalWidth > originalHeight) {
76 | imageLeft = (cropBoxRect.width - imageWidth) / 2 +cropBoxRect.left;
77 | imageTop = cropBoxRect.top;
78 | } else {
79 | imageLeft = cropBoxRect.left;
80 | imageTop = (cropBoxRect.height - imageHeight) / 2 + cropBoxRect.top;
81 | }
82 |
83 | self.moveImage(imageLeft, imageTop);
84 |
85 | self.imageLoading = false;
86 | });
87 | };
88 | image.src = url || src;
89 | },
90 |
91 | getFocalPoint: function(event) {
92 | var focalPoint = {
93 | left: (event.touches[0].pageX + event.touches[1].pageX) / 2,
94 | top: (event.touches[0].pageY + event.touches[1].pageY) / 2
95 | };
96 |
97 | var imageState = this.imageState;
98 | var cropBoxRect = this.cropBoxRect;
99 |
100 | focalPoint.left -= cropBoxRect.left + imageState.left;
101 | focalPoint.top -= cropBoxRect.top + imageState.top;
102 |
103 | return focalPoint;
104 | },
105 |
106 | render: function(parentNode) {
107 | var element = document.createElement('div');
108 | element.className = 'cropper';
109 |
110 | var coverStart = document.createElement('div');
111 | var coverEnd = document.createElement('div');
112 | var cropBox = document.createElement('div');
113 | var image = document.createElement('img');
114 |
115 | coverStart.className = 'cover cover-start';
116 | coverEnd.className = 'cover cover-end';
117 | cropBox.className = 'crop-box';
118 |
119 | element.appendChild(coverStart);
120 | element.appendChild(coverEnd);
121 | element.appendChild(cropBox);
122 | element.appendChild(image);
123 |
124 | this.refs = {
125 | element: element,
126 | coverStart: coverStart,
127 | coverEnd: coverEnd,
128 | cropBox: cropBox,
129 | image: image
130 | };
131 |
132 | if (parentNode) {
133 | parentNode.appendChild(element);
134 | }
135 |
136 | if (element.offsetHeight > 0) {
137 | this.resetSize();
138 | }
139 |
140 | this.bindEvents();
141 | },
142 |
143 | initScale: function () {
144 | var cropBoxRect = this.cropBoxRect;
145 | var width = this.imageState.width;
146 | var height = this.imageState.height;
147 | var scale, minScale;
148 |
149 | if (width > height) {
150 | scale = this.imageState.scale = cropBoxRect.height / height;
151 | minScale = cropBoxRect.height * 0.8 / height;
152 | } else {
153 | scale = this.imageState.scale = cropBoxRect.width / width;
154 | minScale = cropBoxRect.width * 0.8 / width;
155 | }
156 |
157 | this.scaleRange = [scale, 2];
158 | this.bounceScaleRange = [minScale, 3];
159 | },
160 |
161 | resetSize: function() {
162 | var refs = this.refs;
163 | if (!refs) return;
164 |
165 | var element = refs.element;
166 | var cropBox = refs.cropBox;
167 | var coverStart = refs.coverStart;
168 | var coverEnd = refs.coverEnd;
169 |
170 | var width = element.offsetWidth;
171 | var height = element.offsetHeight;
172 |
173 | if (width > height) {
174 | element.className = 'cropper cropper-horizontal';
175 |
176 | coverStart.style.width = coverEnd.style.width = (width - height) / 2 + 'px';
177 | coverStart.style.height = coverEnd.style.height = '';
178 | cropBox.style.width = cropBox.style.height = height + 'px';
179 | } else {
180 | element.className = 'cropper';
181 |
182 | coverStart.style.height = coverEnd.style.height = (height - width) / 2 + 'px';
183 | coverStart.style.width = coverEnd.style.width = '';
184 | cropBox.style.width = cropBox.style.height = width + 'px';
185 | }
186 |
187 | var elementRect = element.getBoundingClientRect();
188 | var cropBoxRect = cropBox.getBoundingClientRect();
189 |
190 | this.cropBoxRect = {
191 | left: cropBoxRect.left - elementRect.left,
192 | top: cropBoxRect.top - elementRect.top,
193 | width: cropBoxRect.width,
194 | height: cropBoxRect.height
195 | };
196 |
197 | this.initScale();
198 |
199 | this.checkBounce(0);
200 | },
201 |
202 | checkBounce: function (speed) {
203 | var imageState = this.imageState;
204 | var cropBoxRect = this.cropBoxRect;
205 |
206 | var imageWidth = imageState.width;
207 | var imageHeight = imageState.height;
208 | var imageScale = imageState.scale;
209 |
210 | var imageOffset = getElementTranslate(this.refs.image);
211 | var left = imageOffset.left;
212 | var top = imageOffset.top;
213 |
214 | var leftRange = [-imageWidth * imageScale + cropBoxRect.width + cropBoxRect.left, cropBoxRect.left];
215 | var topRange = [-imageHeight * imageScale + cropBoxRect.height + cropBoxRect.top, cropBoxRect.top];
216 |
217 | var overflow = false;
218 |
219 | if (left < leftRange[0]) {
220 | left = leftRange[0];
221 | overflow = true;
222 | } else if (left > leftRange[1]) {
223 | left = leftRange[1];
224 | overflow = true;
225 | }
226 |
227 | if (top < topRange[0]) {
228 | top = topRange[0];
229 | overflow = true;
230 | } else if (top > topRange[1]) {
231 | top = topRange[1];
232 | overflow = true;
233 | }
234 |
235 | if (overflow) {
236 | var self = this;
237 | translate(this.refs.image, left, top, speed === undefined ? 200 : 0, function() {
238 | self.moveImage(left, top);
239 | });
240 | }
241 | },
242 |
243 | moveImage: function(left, top) {
244 | var image = this.refs.image;
245 | translateElement(image, left, top);
246 |
247 | this.imageState.left = left;
248 | this.imageState.top = top;
249 | },
250 |
251 | onTouchStart: function(event) {
252 | this.amplitude = 0;
253 | var image = this.refs.image;
254 |
255 | var fingerCount = event.touches.length;
256 | if (fingerCount) {
257 | var touchEvent = event.touches[0];
258 |
259 | var imageOffset = getElementTranslate(image);
260 |
261 | this.dragState = {
262 | timestamp: Date.now(),
263 | startTouchLeft: touchEvent.pageX,
264 | startTouchTop: touchEvent.pageY,
265 | startLeft: imageOffset.left || 0,
266 | startTop: imageOffset.top || 0
267 | };
268 | }
269 |
270 | if (fingerCount >= 2) {
271 | var zoomState = this.zoomState = {
272 | timestamp: Date.now()
273 | };
274 |
275 | zoomState.startDistance = getDistance(event);
276 | zoomState.focalPoint = this.getFocalPoint(event);
277 | }
278 | },
279 |
280 | onTouchMove: function(event) {
281 | var fingerCount = event.touches.length;
282 |
283 | var touchEvent = event.touches[0];
284 |
285 | var cropBoxRect = this.cropBoxRect;
286 | var image = this.refs.image;
287 |
288 | var imageState = this.imageState;
289 | var imageWidth = imageState.width;
290 | var imageHeight = imageState.height;
291 |
292 | var dragState = this.dragState;
293 | var zoomState = this.zoomState;
294 |
295 | if (fingerCount === 1) {
296 | var leftRange = [ -imageWidth * imageState.scale + cropBoxRect.width, cropBoxRect.left ];
297 | var topRange = [ -imageHeight * imageState.scale + cropBoxRect.height + cropBoxRect.top, cropBoxRect.top ];
298 |
299 | var deltaX = touchEvent.pageX - (dragState.lastLeft || dragState.startTouchLeft);
300 | var deltaY = touchEvent.pageY - (dragState.lastTop || dragState.startTouchTop);
301 |
302 | var imageOffset = getElementTranslate(image);
303 |
304 | var left = imageOffset.left + deltaX;
305 | var top = imageOffset.top + deltaY;
306 |
307 | if (left < leftRange[0] || left > leftRange[1]) {
308 | left -= deltaX / 2;
309 | }
310 |
311 | if (top < topRange [0] || top > topRange[1]) {
312 | top -= deltaY / 2;
313 | }
314 |
315 | this.moveImage(left, top);
316 | } else if (fingerCount >= 2) {
317 | if (!zoomState.timestamp) {
318 | zoomState = {
319 | timestamp: Date.now()
320 | };
321 |
322 | zoomState.startDistance = getDistance(event);
323 | zoomState.focalPoint = this.getFocalPoint(event);
324 |
325 | return;
326 | }
327 |
328 | var newDistance = getDistance(event);
329 | var oldScale = imageState.scale;
330 |
331 | imageState.scale = oldScale * newDistance / (zoomState.lastDistance || zoomState.startDistance);
332 |
333 | var scaleRange = this.scaleRange;
334 | if (imageState.scale < scaleRange[0]) {
335 | imageState.scale = scaleRange[0];
336 | } else if (imageState.scale > scaleRange[1]) {
337 | imageState.scale = scaleRange[1];
338 | }
339 |
340 | this.zoomWithFocal(oldScale);
341 |
342 | zoomState.focalPoint = this.getFocalPoint(event);
343 | zoomState.lastDistance = newDistance;
344 | }
345 |
346 | dragState.lastLeft = touchEvent.pageX;
347 | dragState.lastTop = touchEvent.pageY;
348 | },
349 |
350 | onTouchEnd: function(event) {
351 | var imageState = this.imageState;
352 | var zoomState = this.zoomState;
353 | var dragState = this.dragState;
354 | var amplitude = this.amplitude;
355 | var imageWidth = imageState.width;
356 | var imageHeight = imageState.height;
357 | var cropBoxRect = this.cropBoxRect;
358 |
359 | if (event.touches.length === 0 && dragState.timestamp) {
360 | var self = this;
361 | var duration = Date.now() - dragState.timestamp;
362 |
363 | if (duration > 300) {
364 | self.checkBounce();
365 | } else {
366 | var target;
367 |
368 | var top = imageState.top;
369 | var left = imageState.left;
370 |
371 | var momentumVertical = false;
372 |
373 | var timeConstant = 160;
374 |
375 | var autoScroll = function () {
376 | var elapsed, delta;
377 |
378 | if (amplitude) {
379 | elapsed = Date.now() - timestamp;
380 | delta = -amplitude * Math.exp(-elapsed / timeConstant);
381 | if (delta > 0.5 || delta < -0.5) {
382 | if (momentumVertical) {
383 | self.moveImage(left, target + delta);
384 | } else {
385 | self.moveImage(target + delta, top);
386 | }
387 |
388 | requestAnimationFrame(autoScroll);
389 | } else {
390 | var currentLeft;
391 | var currentTop;
392 |
393 | if (momentumVertical) {
394 | currentLeft = left;
395 | currentTop = target;
396 | } else {
397 | currentLeft = target;
398 | currentTop = top;
399 | }
400 |
401 | self.moveImage(currentLeft, currentTop);
402 | self.checkBounce();
403 | }
404 | }
405 | };
406 |
407 | var velocity;
408 |
409 | var deltaX = event.changedTouches[0].pageX - dragState.startTouchLeft;
410 | var deltaY = event.changedTouches[0].pageY - dragState.startTouchTop;
411 |
412 | if (Math.abs(deltaX) > Math.abs(deltaY)) {
413 | velocity = deltaX / duration;
414 | } else {
415 | momentumVertical = true;
416 | velocity = deltaY / duration;
417 | }
418 |
419 | amplitude = 80 * velocity;
420 |
421 | var range;
422 |
423 | if (momentumVertical) {
424 | target = Math.round(imageState.top + amplitude);
425 | range = [-imageHeight * imageState.scale + cropBoxRect.height / 2 + cropBoxRect.top, cropBoxRect.top + cropBoxRect.height / 2];
426 | } else {
427 | target = Math.round(imageState.left + amplitude);
428 | range = [-imageWidth * imageState.scale + cropBoxRect.width / 2, cropBoxRect.left + cropBoxRect.width / 2];
429 | }
430 |
431 | if (target < range[0]) {
432 | target = range[0];
433 | amplitude /= 2;
434 | } else if (target > range[1]) {
435 | target = range[1];
436 | amplitude /= 2;
437 | }
438 |
439 | var timestamp = Date.now();
440 | requestAnimationFrame(autoScroll);
441 | }
442 |
443 | this.dragState = {};
444 | } else if (zoomState.timestamp) {
445 | this.checkBounce();
446 |
447 | this.zoomState = {};
448 | }
449 | },
450 |
451 | zoomWithFocal: function(oldScale) {
452 | var image = this.refs.image;
453 | var imageState = this.imageState;
454 | var imageScale = imageState.scale;
455 |
456 | image.style.width = imageState.width * imageScale + 'px';
457 | image.style.height = imageState.height * imageScale + 'px';
458 |
459 | var focalPoint = this.zoomState.focalPoint;
460 |
461 | var offsetLeft = (focalPoint.left / imageScale - focalPoint.left / oldScale) * imageScale;
462 | var offsetTop = (focalPoint.top / imageScale - focalPoint.top / oldScale) * imageScale;
463 |
464 | var imageLeft = imageState.left || 0;
465 | var imageTop = imageState.top || 0;
466 |
467 | this.moveImage(imageLeft + offsetLeft, imageTop + offsetTop);
468 | },
469 |
470 | bindEvents: function() {
471 | var cropBox = this.refs.cropBox;
472 |
473 | cropBox.addEventListener('touchstart', this.onTouchStart.bind(this));
474 |
475 | cropBox.addEventListener('touchmove', this.onTouchMove.bind(this));
476 |
477 | cropBox.addEventListener('touchend', this.onTouchEnd.bind(this));
478 | },
479 |
480 | createBase64: function (callback, width) {
481 | var imageState = this.imageState;
482 | var cropBoxRect = this.cropBoxRect;
483 | var scale = imageState.scale;
484 |
485 | var canvasSize = width;
486 |
487 | if (!canvasSize) {
488 | canvasSize = cropBoxRect.width * 2;
489 | }
490 |
491 | var imageLeft = Math.round((cropBoxRect.left - imageState.left) / scale);
492 | var imageTop = Math.round((cropBoxRect.top - imageState.top) / scale);
493 | var imageSize = Math.floor(cropBoxRect.width / scale);
494 |
495 | var orientation = this.orientation;
496 | var image = this.refs.image;
497 |
498 | var cropImage = new Image();
499 | cropImage.src = image.src;
500 |
501 | cropImage.onload = function() {
502 | var resultCanvas = loadImage.scale(cropImage, {
503 | canvas: true,
504 | left: imageLeft,
505 | top: imageTop,
506 | sourceWidth: imageSize,
507 | sourceHeight: imageSize,
508 | orientation: orientation,
509 | maxWidth: canvasSize,
510 | maxHeight: canvasSize
511 | });
512 |
513 | var dataURL = resultCanvas.toDataURL();
514 | if (typeof callback === 'function') {
515 | callback({
516 | canvasSize: canvasSize,
517 | canvas: resultCanvas,
518 | dataURL: dataURL
519 | });
520 | }
521 | };
522 | },
523 |
524 | getCroppedImage: function(callback, width) {
525 | if (!this.image) return null;
526 |
527 | this.createBase64(function(result) {
528 | var canvasSize = result.canvasSize;
529 | var canvas = result.canvas;
530 | var dataURL = result.dataURL;
531 |
532 | if (typeof callback === 'function') {
533 | callback({
534 | file: canvas.toBlob ? canvas.toBlob() : dataURItoBlob(dataURL),
535 | dataUrl: dataURL,
536 | oDataURL: result.oDataURL,
537 | size: canvasSize
538 | });
539 | }
540 | }, width);
541 | }
542 | };
543 |
544 | module.exports = Cropper;
545 | },{"./util":3}],2:[function(require,module,exports){
546 | window.Cropper = require('./cropper');
547 | },{"./cropper":1}],3:[function(require,module,exports){
548 |
549 | var once = function(el, event, fn) {
550 | var listener = function() {
551 | if (fn) {
552 | fn.apply(this, arguments);
553 | }
554 | el.removeEventListener(event, listener);
555 | };
556 | el.addEventListener(event, listener);
557 | };
558 |
559 | module.exports = {
560 | dataURItoBlob: function (dataURI) {
561 | var binaryString = atob(dataURI.split(',')[1]);
562 | var arrayBuffer = new ArrayBuffer(binaryString.length);
563 | var intArray = new Uint8Array(arrayBuffer);
564 |
565 | for (var i = 0, j = binaryString.length; i < j; i++) {
566 | intArray[i] = binaryString.charCodeAt(i);
567 | }
568 |
569 | var data = [intArray];
570 | var type = 'image/png';
571 |
572 | var result;
573 |
574 | try {
575 | result = new Blob(data, { type: type });
576 | } catch(error) {
577 | // TypeError old chrome and FF
578 | window.BlobBuilder = window.BlobBuilder ||
579 | window.WebKitBlobBuilder ||
580 | window.MozBlobBuilder ||
581 | window.MSBlobBuilder;
582 |
583 | if(error.name == 'TypeError' && window.BlobBuilder){
584 | var builder = new BlobBuilder();
585 | builder.append(arrayBuffer);
586 | result = builder.getBlob(type);
587 | }
588 | }
589 |
590 | return result;
591 | },
592 | getTouchDistance: function(event) {
593 | var finger = event.touches[0];
594 | var finger2 = event.touches[1];
595 |
596 | var c1 = Math.abs(finger.pageX - finger2.pageX);
597 | var c2 = Math.abs(finger.pageY - finger2.pageY);
598 |
599 | return Math.sqrt( c1 * c1 + c2 * c2 );
600 | },
601 | translate: function(element, left, top, speed, callback) {
602 | element.style.webkitTransform = 'translate3d(' + (left || 0) + 'px, ' + (top || 0) + 'px, 0)';
603 | if (speed) {
604 | var called = false;
605 |
606 | var realCallback = function() {
607 | if (called) return;
608 | element.style.webkitTransition = '';
609 | called = true;
610 | if (callback) {
611 | callback.apply(this, arguments);
612 | }
613 | };
614 | element.style.webkitTransition = '-webkit-transform ' + speed + 'ms cubic-bezier(0.325, 0.770, 0.000, 1.000)';
615 | once(element, 'webkitTransitionEnd', realCallback);
616 | once(element, 'transitionend', realCallback);
617 | // for android...
618 | setTimeout(realCallback, speed + 50);
619 | } else {
620 | element.style.webkitTransition = '';
621 | }
622 | },
623 | translateElement: function(element, left, top) {
624 | element.style.webkitTransform = 'translate3d(' + (left || 0) + 'px, ' + (top || 0) + 'px, 0)';
625 | },
626 | getElementTranslate: function(element) {
627 | var transform = element.style.webkitTransform;
628 | var matches = /translate3d\((.*?)\)/ig.exec(transform);
629 | if (matches) {
630 | var translates = matches[1].split(',');
631 | return {
632 | left: parseInt(translates[0], 10),
633 | top: parseInt(translates[1], 10)
634 | }
635 | }
636 | return {
637 | left: 0,
638 | top: 0
639 | }
640 | }
641 | };
642 | },{}]},{},[2])
643 | //# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy93YXRjaGlmeS9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwic3JjL2Nyb3BwZXIuanMiLCJzcmMvaW5kZXguanMiLCJzcmMvdXRpbC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5aEJBOztBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbnZhciB0cmFuc2xhdGVFbGVtZW50ID0gdXRpbC50cmFuc2xhdGVFbGVtZW50O1xudmFyIGdldEVsZW1lbnRUcmFuc2xhdGUgPSB1dGlsLmdldEVsZW1lbnRUcmFuc2xhdGU7XG52YXIgZ2V0RGlzdGFuY2UgPSB1dGlsLmdldFRvdWNoRGlzdGFuY2U7XG52YXIgdHJhbnNsYXRlID0gdXRpbC50cmFuc2xhdGU7XG52YXIgZGF0YVVSSXRvQmxvYiA9IHV0aWwuZGF0YVVSSXRvQmxvYjtcbnZhciBVUkxBcGkgPSB3aW5kb3cuY3JlYXRlT2JqZWN0VVJMICYmIHdpbmRvdyB8fCB3aW5kb3cuVVJMICYmIFVSTC5yZXZva2VPYmplY3RVUkwgJiYgVVJMIHx8IHdpbmRvdy53ZWJraXRVUkwgJiYgd2Via2l0VVJMO1xuXG52YXIgQ3JvcHBlciA9IGZ1bmN0aW9uKCkge1xuICBpZiAoISgnb250b3VjaHN0YXJ0JyBpbiB3aW5kb3cpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCd0aGlzIGRlbW8gc2hvdWxkIHJ1biBpbiBtb2JpbGUgZGV2aWNlJyk7XG4gIH1cblxuICB0aGlzLmltYWdlU3RhdGUgPSB7fTtcbn07XG5cbkNyb3BwZXIucHJvdG90eXBlID0ge1xuICBjb25zdHJ1Y3RvcjogQ3JvcHBlcixcblxuICBzZXRJbWFnZTogZnVuY3Rpb24oc3JjLCBmaWxlKSB7XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHNlbGYuaW1hZ2VMb2FkaW5nID0gdHJ1ZTtcbiAgICBzZWxmLmltYWdlID0gc3JjO1xuXG4gICAgc2VsZi5yZXNldFNpemUoKTtcblxuICAgIHZhciB1cmw7XG4gICAgaWYgKGZpbGUpIHtcbiAgICAgIHVybCA9IFVSTEFwaS5jcmVhdGVPYmplY3RVUkwoZmlsZSk7XG4gICAgfVxuXG4gICAgdmFyIGltYWdlID0gbmV3IEltYWdlKCk7XG5cbiAgICBpbWFnZS5vbmxvYWQgPSBmdW5jdGlvbigpIHtcbiAgICAgIHZhciBzZWxmSW1hZ2UgPSBzZWxmLnJlZnMuaW1hZ2U7XG5cbiAgICAgIGxvYWRJbWFnZS5wYXJzZU1ldGFEYXRhKGZpbGUsIGZ1bmN0aW9uKGRhdGEpIHtcbiAgICAgICAgdmFyIG9yaWVudGF0aW9uO1xuICAgICAgICBpZiAoZGF0YS5leGlmKSB7XG4gICAgICAgICAgb3JpZW50YXRpb24gPSBkYXRhLmV4aWZbMHgwMTEyXTtcbiAgICAgICAgfVxuXG4gICAgICAgIHNlbGZJbWFnZS5zcmMgPSBzcmM7XG4gICAgICAgIHNlbGYub3JpZW50YXRpb24gPSBvcmllbnRhdGlvbjtcblxuICAgICAgICB2YXIgb3JpZ2luYWxXaWR0aCwgb3JpZ2luYWxIZWlnaHQ7XG5cbiAgICAgICAgc2VsZi5pbWFnZVN0YXRlLmxlZnQgPSBzZWxmLmltYWdlU3RhdGUudG9wID0gMDtcblxuICAgICAgICBpZiAoXCI1Njc4XCIuaW5kZXhPZihvcmllbnRhdGlvbikgPiAtMSkge1xuICAgICAgICAgIG9yaWdpbmFsV2lkdGggPSBpbWFnZS5oZWlnaHQ7XG4gICAgICAgICAgb3JpZ2luYWxIZWlnaHQgPSBpbWFnZS53aWR0aDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBvcmlnaW5hbFdpZHRoID0gaW1hZ2Uud2lkdGg7XG4gICAgICAgICAgb3JpZ2luYWxIZWlnaHQgPSBpbWFnZS5oZWlnaHQ7XG4gICAgICAgIH1cblxuICAgICAgICBzZWxmLmltYWdlU3RhdGUud2lkdGggPSBvcmlnaW5hbFdpZHRoO1xuICAgICAgICBzZWxmLmltYWdlU3RhdGUuaGVpZ2h0ID0gb3JpZ2luYWxIZWlnaHQ7XG5cbiAgICAgICAgc2VsZi5pbml0U2NhbGUoKTtcblxuICAgICAgICB2YXIgbWluU2NhbGUgPSBzZWxmLnNjYWxlUmFuZ2VbMF07XG4gICAgICAgIHZhciBpbWFnZVdpZHRoID0gbWluU2NhbGUgKiBvcmlnaW5hbFdpZHRoO1xuICAgICAgICB2YXIgaW1hZ2VIZWlnaHQgPSBtaW5TY2FsZSAqIG9yaWdpbmFsSGVpZ2h0O1xuICAgICAgICBzZWxmSW1hZ2Uuc3R5bGUud2lkdGggPSBpbWFnZVdpZHRoICsgJ3B4JztcbiAgICAgICAgc2VsZkltYWdlLnN0eWxlLmhlaWdodCA9IGltYWdlSGVpZ2h0ICsgJ3B4JztcblxuICAgICAgICB2YXIgaW1hZ2VMZWZ0LCBpbWFnZVRvcDtcblxuICAgICAgICB2YXIgY3JvcEJveFJlY3QgPSBzZWxmLmNyb3BCb3hSZWN0O1xuXG4gICAgICAgIGlmIChvcmlnaW5hbFdpZHRoID4gb3JpZ2luYWxIZWlnaHQpIHtcbiAgICAgICAgICBpbWFnZUxlZnQgPSAoY3JvcEJveFJlY3Qud2lkdGggLSBpbWFnZVdpZHRoKSAvIDIgK2Nyb3BCb3hSZWN0LmxlZnQ7XG4gICAgICAgICAgaW1hZ2VUb3AgPSBjcm9wQm94UmVjdC50b3A7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgaW1hZ2VMZWZ0ID0gY3JvcEJveFJlY3QubGVmdDtcbiAgICAgICAgICBpbWFnZVRvcCA9IChjcm9wQm94UmVjdC5oZWlnaHQgLSBpbWFnZUhlaWdodCkgLyAyICsgY3JvcEJveFJlY3QudG9wO1xuICAgICAgICB9XG5cbiAgICAgICAgc2VsZi5tb3ZlSW1hZ2UoaW1hZ2VMZWZ0LCBpbWFnZVRvcCk7XG5cbiAgICAgICAgc2VsZi5pbWFnZUxvYWRpbmcgPSBmYWxzZTtcbiAgICAgIH0pO1xuICAgIH07XG4gICAgaW1hZ2Uuc3JjID0gdXJsIHx8IHNyYztcbiAgfSxcblxuICBnZXRGb2NhbFBvaW50OiBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBmb2NhbFBvaW50ID0ge1xuICAgICAgbGVmdDogKGV2ZW50LnRvdWNoZXNbMF0ucGFnZVggKyBldmVudC50b3VjaGVzWzFdLnBhZ2VYKSAvIDIsXG4gICAgICB0b3A6IChldmVudC50b3VjaGVzWzBdLnBhZ2VZICsgZXZlbnQudG91Y2hlc1sxXS5wYWdlWSkgLyAyXG4gICAgfTtcblxuICAgIHZhciBpbWFnZVN0YXRlID0gdGhpcy5pbWFnZVN0YXRlO1xuICAgIHZhciBjcm9wQm94UmVjdCA9IHRoaXMuY3JvcEJveFJlY3Q7XG5cbiAgICBmb2NhbFBvaW50LmxlZnQgLT0gY3JvcEJveFJlY3QubGVmdCArIGltYWdlU3RhdGUubGVmdDtcbiAgICBmb2NhbFBvaW50LnRvcCAtPSBjcm9wQm94UmVjdC50b3AgKyBpbWFnZVN0YXRlLnRvcDtcblxuICAgIHJldHVybiBmb2NhbFBvaW50O1xuICB9LFxuXG4gIHJlbmRlcjogZnVuY3Rpb24ocGFyZW50Tm9kZSkge1xuICAgIHZhciBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgZWxlbWVudC5jbGFzc05hbWUgPSAnY3JvcHBlcic7XG5cbiAgICB2YXIgY292ZXJTdGFydCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpO1xuICAgIHZhciBjb3ZlckVuZCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpO1xuICAgIHZhciBjcm9wQm94ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgdmFyIGltYWdlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnaW1nJyk7XG5cbiAgICBjb3ZlclN0YXJ0LmNsYXNzTmFtZSA9ICdjb3ZlciBjb3Zlci1zdGFydCc7XG4gICAgY292ZXJFbmQuY2xhc3NOYW1lID0gJ2NvdmVyIGNvdmVyLWVuZCc7XG4gICAgY3JvcEJveC5jbGFzc05hbWUgPSAnY3JvcC1ib3gnO1xuXG4gICAgZWxlbWVudC5hcHBlbmRDaGlsZChjb3ZlclN0YXJ0KTtcbiAgICBlbGVtZW50LmFwcGVuZENoaWxkKGNvdmVyRW5kKTtcbiAgICBlbGVtZW50LmFwcGVuZENoaWxkKGNyb3BCb3gpO1xuICAgIGVsZW1lbnQuYXBwZW5kQ2hpbGQoaW1hZ2UpO1xuXG4gICAgdGhpcy5yZWZzID0ge1xuICAgICAgZWxlbWVudDogZWxlbWVudCxcbiAgICAgIGNvdmVyU3RhcnQ6IGNvdmVyU3RhcnQsXG4gICAgICBjb3ZlckVuZDogY292ZXJFbmQsXG4gICAgICBjcm9wQm94OiBjcm9wQm94LFxuICAgICAgaW1hZ2U6IGltYWdlXG4gICAgfTtcblxuICAgIGlmIChwYXJlbnROb2RlKSB7XG4gICAgICBwYXJlbnROb2RlLmFwcGVuZENoaWxkKGVsZW1lbnQpO1xuICAgIH1cblxuICAgIGlmIChlbGVtZW50Lm9mZnNldEhlaWdodCA+IDApIHtcbiAgICAgIHRoaXMucmVzZXRTaXplKCk7XG4gICAgfVxuXG4gICAgdGhpcy5iaW5kRXZlbnRzKCk7XG4gIH0sXG5cbiAgaW5pdFNjYWxlOiBmdW5jdGlvbiAoKSB7XG4gICAgdmFyIGNyb3BCb3hSZWN0ID0gdGhpcy5jcm9wQm94UmVjdDtcbiAgICB2YXIgd2lkdGggPSB0aGlzLmltYWdlU3RhdGUud2lkdGg7XG4gICAgdmFyIGhlaWdodCA9IHRoaXMuaW1hZ2VTdGF0ZS5oZWlnaHQ7XG4gICAgdmFyIHNjYWxlLCBtaW5TY2FsZTtcblxuICAgIGlmICh3aWR0aCA+IGhlaWdodCkge1xuICAgICAgc2NhbGUgPSB0aGlzLmltYWdlU3RhdGUuc2NhbGUgPSBjcm9wQm94UmVjdC5oZWlnaHQgLyBoZWlnaHQ7XG4gICAgICBtaW5TY2FsZSA9IGNyb3BCb3hSZWN0LmhlaWdodCAqIDAuOCAvIGhlaWdodDtcbiAgICB9IGVsc2Uge1xuICAgICAgc2NhbGUgPSB0aGlzLmltYWdlU3RhdGUuc2NhbGUgPSBjcm9wQm94UmVjdC53aWR0aCAvIHdpZHRoO1xuICAgICAgbWluU2NhbGUgPSBjcm9wQm94UmVjdC53aWR0aCAqIDAuOCAvIHdpZHRoO1xuICAgIH1cblxuICAgIHRoaXMuc2NhbGVSYW5nZSA9IFtzY2FsZSwgMl07XG4gICAgdGhpcy5ib3VuY2VTY2FsZVJhbmdlID0gW21pblNjYWxlLCAzXTtcbiAgfSxcblxuICByZXNldFNpemU6IGZ1bmN0aW9uKCkge1xuICAgIHZhciByZWZzID0gdGhpcy5yZWZzO1xuICAgIGlmICghcmVmcykgcmV0dXJuO1xuXG4gICAgdmFyIGVsZW1lbnQgPSByZWZzLmVsZW1lbnQ7XG4gICAgdmFyIGNyb3BCb3ggPSByZWZzLmNyb3BCb3g7XG4gICAgdmFyIGNvdmVyU3RhcnQgPSByZWZzLmNvdmVyU3RhcnQ7XG4gICAgdmFyIGNvdmVyRW5kID0gcmVmcy5jb3ZlckVuZDtcblxuICAgIHZhciB3aWR0aCA9IGVsZW1lbnQub2Zmc2V0V2lkdGg7XG4gICAgdmFyIGhlaWdodCA9IGVsZW1lbnQub2Zmc2V0SGVpZ2h0O1xuXG4gICAgaWYgKHdpZHRoID4gaGVpZ2h0KSB7XG4gICAgICBlbGVtZW50LmNsYXNzTmFtZSA9ICdjcm9wcGVyIGNyb3BwZXItaG9yaXpvbnRhbCc7XG5cbiAgICAgIGNvdmVyU3RhcnQuc3R5bGUud2lkdGggPSBjb3ZlckVuZC5zdHlsZS53aWR0aCA9ICh3aWR0aCAtIGhlaWdodCkgLyAyICsgJ3B4JztcbiAgICAgIGNvdmVyU3RhcnQuc3R5bGUuaGVpZ2h0ID0gY292ZXJFbmQuc3R5bGUuaGVpZ2h0ID0gJyc7XG4gICAgICBjcm9wQm94LnN0eWxlLndpZHRoID0gY3JvcEJveC5zdHlsZS5oZWlnaHQgPSBoZWlnaHQgKyAncHgnO1xuICAgIH0gZWxzZSB7XG4gICAgICBlbGVtZW50LmNsYXNzTmFtZSA9ICdjcm9wcGVyJztcblxuICAgICAgY292ZXJTdGFydC5zdHlsZS5oZWlnaHQgPSBjb3ZlckVuZC5zdHlsZS5oZWlnaHQgPSAoaGVpZ2h0IC0gd2lkdGgpIC8gMiArICdweCc7XG4gICAgICBjb3ZlclN0YXJ0LnN0eWxlLndpZHRoID0gY292ZXJFbmQuc3R5bGUud2lkdGggPSAnJztcbiAgICAgIGNyb3BCb3guc3R5bGUud2lkdGggPSBjcm9wQm94LnN0eWxlLmhlaWdodCA9IHdpZHRoICsgJ3B4JztcbiAgICB9XG5cbiAgICB2YXIgZWxlbWVudFJlY3QgPSBlbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgIHZhciBjcm9wQm94UmVjdCA9IGNyb3BCb3guZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cbiAgICB0aGlzLmNyb3BCb3hSZWN0ID0ge1xuICAgICAgbGVmdDogY3JvcEJveFJlY3QubGVmdCAtIGVsZW1lbnRSZWN0LmxlZnQsXG4gICAgICB0b3A6IGNyb3BCb3hSZWN0LnRvcCAtIGVsZW1lbnRSZWN0LnRvcCxcbiAgICAgIHdpZHRoOiBjcm9wQm94UmVjdC53aWR0aCxcbiAgICAgIGhlaWdodDogY3JvcEJveFJlY3QuaGVpZ2h0XG4gICAgfTtcblxuICAgIHRoaXMuaW5pdFNjYWxlKCk7XG5cbiAgICB0aGlzLmNoZWNrQm91bmNlKDApO1xuICB9LFxuXG4gIGNoZWNrQm91bmNlOiBmdW5jdGlvbiAoc3BlZWQpIHtcbiAgICB2YXIgaW1hZ2VTdGF0ZSA9IHRoaXMuaW1hZ2VTdGF0ZTtcbiAgICB2YXIgY3JvcEJveFJlY3QgPSB0aGlzLmNyb3BCb3hSZWN0O1xuXG4gICAgdmFyIGltYWdlV2lkdGggPSBpbWFnZVN0YXRlLndpZHRoO1xuICAgIHZhciBpbWFnZUhlaWdodCA9IGltYWdlU3RhdGUuaGVpZ2h0O1xuICAgIHZhciBpbWFnZVNjYWxlID0gaW1hZ2VTdGF0ZS5zY2FsZTtcblxuICAgIHZhciBpbWFnZU9mZnNldCA9IGdldEVsZW1lbnRUcmFuc2xhdGUodGhpcy5yZWZzLmltYWdlKTtcbiAgICB2YXIgbGVmdCA9IGltYWdlT2Zmc2V0LmxlZnQ7XG4gICAgdmFyIHRvcCA9IGltYWdlT2Zmc2V0LnRvcDtcblxuICAgIHZhciBsZWZ0UmFuZ2UgPSBbLWltYWdlV2lkdGggKiBpbWFnZVNjYWxlICsgY3JvcEJveFJlY3Qud2lkdGggKyBjcm9wQm94UmVjdC5sZWZ0LCBjcm9wQm94UmVjdC5sZWZ0XTtcbiAgICB2YXIgdG9wUmFuZ2UgPSBbLWltYWdlSGVpZ2h0ICogaW1hZ2VTY2FsZSArIGNyb3BCb3hSZWN0LmhlaWdodCArIGNyb3BCb3hSZWN0LnRvcCwgY3JvcEJveFJlY3QudG9wXTtcblxuICAgIHZhciBvdmVyZmxvdyA9IGZhbHNlO1xuXG4gICAgaWYgKGxlZnQgPCBsZWZ0UmFuZ2VbMF0pIHtcbiAgICAgIGxlZnQgPSBsZWZ0UmFuZ2VbMF07XG4gICAgICBvdmVyZmxvdyA9IHRydWU7XG4gICAgfSBlbHNlIGlmIChsZWZ0ID4gbGVmdFJhbmdlWzFdKSB7XG4gICAgICBsZWZ0ID0gbGVmdFJhbmdlWzFdO1xuICAgICAgb3ZlcmZsb3cgPSB0cnVlO1xuICAgIH1cblxuICAgIGlmICh0b3AgPCB0b3BSYW5nZVswXSkge1xuICAgICAgdG9wID0gdG9wUmFuZ2VbMF07XG4gICAgICBvdmVyZmxvdyA9IHRydWU7XG4gICAgfSBlbHNlIGlmICh0b3AgPiB0b3BSYW5nZVsxXSkge1xuICAgICAgdG9wID0gdG9wUmFuZ2VbMV07XG4gICAgICBvdmVyZmxvdyA9IHRydWU7XG4gICAgfVxuXG4gICAgaWYgKG92ZXJmbG93KSB7XG4gICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICB0cmFuc2xhdGUodGhpcy5yZWZzLmltYWdlLCBsZWZ0LCB0b3AsIHNwZWVkID09PSB1bmRlZmluZWQgPyAyMDAgOiAwLCBmdW5jdGlvbigpIHtcbiAgICAgICAgc2VsZi5tb3ZlSW1hZ2UobGVmdCwgdG9wKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgfSxcblxuICBtb3ZlSW1hZ2U6IGZ1bmN0aW9uKGxlZnQsIHRvcCkge1xuICAgIHZhciBpbWFnZSA9IHRoaXMucmVmcy5pbWFnZTtcbiAgICB0cmFuc2xhdGVFbGVtZW50KGltYWdlLCBsZWZ0LCB0b3ApO1xuXG4gICAgdGhpcy5pbWFnZVN0YXRlLmxlZnQgPSBsZWZ0O1xuICAgIHRoaXMuaW1hZ2VTdGF0ZS50b3AgPSB0b3A7XG4gIH0sXG5cbiAgb25Ub3VjaFN0YXJ0OiBmdW5jdGlvbihldmVudCkge1xuICAgIHRoaXMuYW1wbGl0dWRlID0gMDtcbiAgICB2YXIgaW1hZ2UgPSB0aGlzLnJlZnMuaW1hZ2U7XG5cbiAgICB2YXIgZmluZ2VyQ291bnQgPSBldmVudC50b3VjaGVzLmxlbmd0aDtcbiAgICBpZiAoZmluZ2VyQ291bnQpIHtcbiAgICAgIHZhciB0b3VjaEV2ZW50ID0gZXZlbnQudG91Y2hlc1swXTtcblxuICAgICAgdmFyIGltYWdlT2Zmc2V0ID0gZ2V0RWxlbWVudFRyYW5zbGF0ZShpbWFnZSk7XG5cbiAgICAgIHRoaXMuZHJhZ1N0YXRlID0ge1xuICAgICAgICB0aW1lc3RhbXA6IERhdGUubm93KCksXG4gICAgICAgIHN0YXJ0VG91Y2hMZWZ0OiB0b3VjaEV2ZW50LnBhZ2VYLFxuICAgICAgICBzdGFydFRvdWNoVG9wOiB0b3VjaEV2ZW50LnBhZ2VZLFxuICAgICAgICBzdGFydExlZnQ6IGltYWdlT2Zmc2V0LmxlZnQgfHwgMCxcbiAgICAgICAgc3RhcnRUb3A6IGltYWdlT2Zmc2V0LnRvcCB8fCAwXG4gICAgICB9O1xuICAgIH1cblxuICAgIGlmIChmaW5nZXJDb3VudCA+PSAyKSB7XG4gICAgICB2YXIgem9vbVN0YXRlID0gdGhpcy56b29tU3RhdGUgPSB7XG4gICAgICAgIHRpbWVzdGFtcDogRGF0ZS5ub3coKVxuICAgICAgfTtcblxuICAgICAgem9vbVN0YXRlLnN0YXJ0RGlzdGFuY2UgPSBnZXREaXN0YW5jZShldmVudCk7XG4gICAgICB6b29tU3RhdGUuZm9jYWxQb2ludCA9IHRoaXMuZ2V0Rm9jYWxQb2ludChldmVudCk7XG4gICAgfVxuICB9LFxuXG4gIG9uVG91Y2hNb3ZlOiBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBmaW5nZXJDb3VudCA9IGV2ZW50LnRvdWNoZXMubGVuZ3RoO1xuXG4gICAgdmFyIHRvdWNoRXZlbnQgPSBldmVudC50b3VjaGVzWzBdO1xuXG4gICAgdmFyIGNyb3BCb3hSZWN0ID0gdGhpcy5jcm9wQm94UmVjdDtcbiAgICB2YXIgaW1hZ2UgPSB0aGlzLnJlZnMuaW1hZ2U7XG5cbiAgICB2YXIgaW1hZ2VTdGF0ZSA9IHRoaXMuaW1hZ2VTdGF0ZTtcbiAgICB2YXIgaW1hZ2VXaWR0aCA9IGltYWdlU3RhdGUud2lkdGg7XG4gICAgdmFyIGltYWdlSGVpZ2h0ID0gaW1hZ2VTdGF0ZS5oZWlnaHQ7XG5cbiAgICB2YXIgZHJhZ1N0YXRlID0gdGhpcy5kcmFnU3RhdGU7XG4gICAgdmFyIHpvb21TdGF0ZSA9IHRoaXMuem9vbVN0YXRlO1xuXG4gICAgaWYgKGZpbmdlckNvdW50ID09PSAxKSB7XG4gICAgICB2YXIgbGVmdFJhbmdlID0gWyAtaW1hZ2VXaWR0aCAqIGltYWdlU3RhdGUuc2NhbGUgKyBjcm9wQm94UmVjdC53aWR0aCwgY3JvcEJveFJlY3QubGVmdCBdO1xuICAgICAgdmFyIHRvcFJhbmdlID0gWyAtaW1hZ2VIZWlnaHQgKiBpbWFnZVN0YXRlLnNjYWxlICsgY3JvcEJveFJlY3QuaGVpZ2h0ICsgY3JvcEJveFJlY3QudG9wLCBjcm9wQm94UmVjdC50b3AgXTtcblxuICAgICAgdmFyIGRlbHRhWCA9IHRvdWNoRXZlbnQucGFnZVggLSAoZHJhZ1N0YXRlLmxhc3RMZWZ0IHx8IGRyYWdTdGF0ZS5zdGFydFRvdWNoTGVmdCk7XG4gICAgICB2YXIgZGVsdGFZID0gdG91Y2hFdmVudC5wYWdlWSAtIChkcmFnU3RhdGUubGFzdFRvcCB8fCBkcmFnU3RhdGUuc3RhcnRUb3VjaFRvcCk7XG5cbiAgICAgIHZhciBpbWFnZU9mZnNldCA9IGdldEVsZW1lbnRUcmFuc2xhdGUoaW1hZ2UpO1xuXG4gICAgICB2YXIgbGVmdCA9IGltYWdlT2Zmc2V0LmxlZnQgKyBkZWx0YVg7XG4gICAgICB2YXIgdG9wID0gaW1hZ2VPZmZzZXQudG9wICsgZGVsdGFZO1xuXG4gICAgICBpZiAobGVmdCA8IGxlZnRSYW5nZVswXSB8fCBsZWZ0ID4gbGVmdFJhbmdlWzFdKSB7XG4gICAgICAgIGxlZnQgLT0gZGVsdGFYIC8gMjtcbiAgICAgIH1cblxuICAgICAgaWYgKHRvcCA8IHRvcFJhbmdlIFswXSB8fCB0b3AgPiB0b3BSYW5nZVsxXSkge1xuICAgICAgICB0b3AgLT0gZGVsdGFZIC8gMjtcbiAgICAgIH1cblxuICAgICAgdGhpcy5tb3ZlSW1hZ2UobGVmdCwgdG9wKTtcbiAgICB9IGVsc2UgaWYgKGZpbmdlckNvdW50ID49IDIpIHtcbiAgICAgIGlmICghem9vbVN0YXRlLnRpbWVzdGFtcCkge1xuICAgICAgICB6b29tU3RhdGUgPSB7XG4gICAgICAgICAgdGltZXN0YW1wOiBEYXRlLm5vdygpXG4gICAgICAgIH07XG5cbiAgICAgICAgem9vbVN0YXRlLnN0YXJ0RGlzdGFuY2UgPSBnZXREaXN0YW5jZShldmVudCk7XG4gICAgICAgIHpvb21TdGF0ZS5mb2NhbFBvaW50ID0gdGhpcy5nZXRGb2NhbFBvaW50KGV2ZW50KTtcblxuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIHZhciBuZXdEaXN0YW5jZSA9IGdldERpc3RhbmNlKGV2ZW50KTtcbiAgICAgIHZhciBvbGRTY2FsZSA9IGltYWdlU3RhdGUuc2NhbGU7XG5cbiAgICAgIGltYWdlU3RhdGUuc2NhbGUgPSBvbGRTY2FsZSAqIG5ld0Rpc3RhbmNlIC8gKHpvb21TdGF0ZS5sYXN0RGlzdGFuY2UgfHwgem9vbVN0YXRlLnN0YXJ0RGlzdGFuY2UpO1xuXG4gICAgICB2YXIgc2NhbGVSYW5nZSA9IHRoaXMuc2NhbGVSYW5nZTtcbiAgICAgIGlmIChpbWFnZVN0YXRlLnNjYWxlIDwgc2NhbGVSYW5nZVswXSkge1xuICAgICAgICBpbWFnZVN0YXRlLnNjYWxlID0gc2NhbGVSYW5nZVswXTtcbiAgICAgIH0gZWxzZSBpZiAoaW1hZ2VTdGF0ZS5zY2FsZSA+IHNjYWxlUmFuZ2VbMV0pIHtcbiAgICAgICAgaW1hZ2VTdGF0ZS5zY2FsZSA9IHNjYWxlUmFuZ2VbMV07XG4gICAgICB9XG5cbiAgICAgIHRoaXMuem9vbVdpdGhGb2NhbChvbGRTY2FsZSk7XG5cbiAgICAgIHpvb21TdGF0ZS5mb2NhbFBvaW50ID0gdGhpcy5nZXRGb2NhbFBvaW50KGV2ZW50KTtcbiAgICAgIHpvb21TdGF0ZS5sYXN0RGlzdGFuY2UgPSBuZXdEaXN0YW5jZTtcbiAgICB9XG5cbiAgICBkcmFnU3RhdGUubGFzdExlZnQgPSB0b3VjaEV2ZW50LnBhZ2VYO1xuICAgIGRyYWdTdGF0ZS5sYXN0VG9wID0gdG91Y2hFdmVudC5wYWdlWTtcbiAgfSxcblxuICBvblRvdWNoRW5kOiBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBpbWFnZVN0YXRlID0gdGhpcy5pbWFnZVN0YXRlO1xuICAgIHZhciB6b29tU3RhdGUgPSB0aGlzLnpvb21TdGF0ZTtcbiAgICB2YXIgZHJhZ1N0YXRlID0gdGhpcy5kcmFnU3RhdGU7XG4gICAgdmFyIGFtcGxpdHVkZSA9IHRoaXMuYW1wbGl0dWRlO1xuICAgIHZhciBpbWFnZVdpZHRoID0gaW1hZ2VTdGF0ZS53aWR0aDtcbiAgICB2YXIgaW1hZ2VIZWlnaHQgPSBpbWFnZVN0YXRlLmhlaWdodDtcbiAgICB2YXIgY3JvcEJveFJlY3QgPSB0aGlzLmNyb3BCb3hSZWN0O1xuXG4gICAgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAwICYmIGRyYWdTdGF0ZS50aW1lc3RhbXApIHtcbiAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgIHZhciBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBkcmFnU3RhdGUudGltZXN0YW1wO1xuXG4gICAgICBpZiAoZHVyYXRpb24gPiAzMDApIHtcbiAgICAgICAgc2VsZi5jaGVja0JvdW5jZSgpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdmFyIHRhcmdldDtcblxuICAgICAgICB2YXIgdG9wID0gaW1hZ2VTdGF0ZS50b3A7XG4gICAgICAgIHZhciBsZWZ0ID0gaW1hZ2VTdGF0ZS5sZWZ0O1xuXG4gICAgICAgIHZhciBtb21lbnR1bVZlcnRpY2FsID0gZmFsc2U7XG5cbiAgICAgICAgdmFyIHRpbWVDb25zdGFudCA9IDE2MDtcblxuICAgICAgICB2YXIgYXV0b1Njcm9sbCA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICB2YXIgZWxhcHNlZCwgZGVsdGE7XG5cbiAgICAgICAgICBpZiAoYW1wbGl0dWRlKSB7XG4gICAgICAgICAgICBlbGFwc2VkID0gRGF0ZS5ub3coKSAtIHRpbWVzdGFtcDtcbiAgICAgICAgICAgIGRlbHRhID0gLWFtcGxpdHVkZSAqIE1hdGguZXhwKC1lbGFwc2VkIC8gdGltZUNvbnN0YW50KTtcbiAgICAgICAgICAgIGlmIChkZWx0YSA+IDAuNSB8fCBkZWx0YSA8IC0wLjUpIHtcbiAgICAgICAgICAgICAgaWYgKG1vbWVudHVtVmVydGljYWwpIHtcbiAgICAgICAgICAgICAgICBzZWxmLm1vdmVJbWFnZShsZWZ0LCB0YXJnZXQgKyBkZWx0YSk7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgc2VsZi5tb3ZlSW1hZ2UodGFyZ2V0ICsgZGVsdGEsIHRvcCk7XG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICByZXF1ZXN0QW5pbWF0aW9uRnJhbWUoYXV0b1Njcm9sbCk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICB2YXIgY3VycmVudExlZnQ7XG4gICAgICAgICAgICAgIHZhciBjdXJyZW50VG9wO1xuXG4gICAgICAgICAgICAgIGlmIChtb21lbnR1bVZlcnRpY2FsKSB7XG4gICAgICAgICAgICAgICAgY3VycmVudExlZnQgPSBsZWZ0O1xuICAgICAgICAgICAgICAgIGN1cnJlbnRUb3AgPSB0YXJnZXQ7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgY3VycmVudExlZnQgPSB0YXJnZXQ7XG4gICAgICAgICAgICAgICAgY3VycmVudFRvcCA9IHRvcDtcbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIHNlbGYubW92ZUltYWdlKGN1cnJlbnRMZWZ0LCBjdXJyZW50VG9wKTtcbiAgICAgICAgICAgICAgc2VsZi5jaGVja0JvdW5jZSgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgICB2YXIgdmVsb2NpdHk7XG5cbiAgICAgICAgdmFyIGRlbHRhWCA9IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzBdLnBhZ2VYIC0gZHJhZ1N0YXRlLnN0YXJ0VG91Y2hMZWZ0O1xuICAgICAgICB2YXIgZGVsdGFZID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbMF0ucGFnZVkgLSBkcmFnU3RhdGUuc3RhcnRUb3VjaFRvcDtcblxuICAgICAgICBpZiAoTWF0aC5hYnMoZGVsdGFYKSA+IE1hdGguYWJzKGRlbHRhWSkpIHtcbiAgICAgICAgICB2ZWxvY2l0eSA9IGRlbHRhWCAvIGR1cmF0aW9uO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIG1vbWVudHVtVmVydGljYWwgPSB0cnVlO1xuICAgICAgICAgIHZlbG9jaXR5ID0gZGVsdGFZIC8gZHVyYXRpb247XG4gICAgICAgIH1cblxuICAgICAgICBhbXBsaXR1ZGUgPSA4MCAqIHZlbG9jaXR5O1xuXG4gICAgICAgIHZhciByYW5nZTtcblxuICAgICAgICBpZiAobW9tZW50dW1WZXJ0aWNhbCkge1xuICAgICAgICAgIHRhcmdldCA9IE1hdGgucm91bmQoaW1hZ2VTdGF0ZS50b3AgKyBhbXBsaXR1ZGUpO1xuICAgICAgICAgIHJhbmdlID0gWy1pbWFnZUhlaWdodCAqIGltYWdlU3RhdGUuc2NhbGUgKyBjcm9wQm94UmVjdC5oZWlnaHQgLyAyICsgY3JvcEJveFJlY3QudG9wLCBjcm9wQm94UmVjdC50b3AgKyBjcm9wQm94UmVjdC5oZWlnaHQgLyAyXTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0YXJnZXQgPSBNYXRoLnJvdW5kKGltYWdlU3RhdGUubGVmdCArIGFtcGxpdHVkZSk7XG4gICAgICAgICAgcmFuZ2UgPSBbLWltYWdlV2lkdGggKiBpbWFnZVN0YXRlLnNjYWxlICsgY3JvcEJveFJlY3Qud2lkdGggLyAyLCBjcm9wQm94UmVjdC5sZWZ0ICsgY3JvcEJveFJlY3Qud2lkdGggLyAyXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0YXJnZXQgPCByYW5nZVswXSkge1xuICAgICAgICAgIHRhcmdldCA9IHJhbmdlWzBdO1xuICAgICAgICAgIGFtcGxpdHVkZSAvPSAyO1xuICAgICAgICB9IGVsc2UgaWYgKHRhcmdldCA+IHJhbmdlWzFdKSB7XG4gICAgICAgICAgdGFyZ2V0ID0gcmFuZ2VbMV07XG4gICAgICAgICAgYW1wbGl0dWRlIC89IDI7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdGltZXN0YW1wID0gRGF0ZS5ub3coKTtcbiAgICAgICAgcmVxdWVzdEFuaW1hdGlvbkZyYW1lKGF1dG9TY3JvbGwpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmRyYWdTdGF0ZSA9IHt9O1xuICAgIH0gZWxzZSBpZiAoem9vbVN0YXRlLnRpbWVzdGFtcCkge1xuICAgICAgdGhpcy5jaGVja0JvdW5jZSgpO1xuXG4gICAgICB0aGlzLnpvb21TdGF0ZSA9IHt9O1xuICAgIH1cbiAgfSxcblxuICB6b29tV2l0aEZvY2FsOiBmdW5jdGlvbihvbGRTY2FsZSkge1xuICAgIHZhciBpbWFnZSA9IHRoaXMucmVmcy5pbWFnZTtcbiAgICB2YXIgaW1hZ2VTdGF0ZSA9IHRoaXMuaW1hZ2VTdGF0ZTtcbiAgICB2YXIgaW1hZ2VTY2FsZSA9IGltYWdlU3RhdGUuc2NhbGU7XG5cbiAgICBpbWFnZS5zdHlsZS53aWR0aCA9IGltYWdlU3RhdGUud2lkdGggKiBpbWFnZVNjYWxlICsgJ3B4JztcbiAgICBpbWFnZS5zdHlsZS5oZWlnaHQgPSBpbWFnZVN0YXRlLmhlaWdodCAqIGltYWdlU2NhbGUgKyAncHgnO1xuXG4gICAgdmFyIGZvY2FsUG9pbnQgPSB0aGlzLnpvb21TdGF0ZS5mb2NhbFBvaW50O1xuXG4gICAgdmFyIG9mZnNldExlZnQgPSAoZm9jYWxQb2ludC5sZWZ0IC8gaW1hZ2VTY2FsZSAtIGZvY2FsUG9pbnQubGVmdCAvIG9sZFNjYWxlKSAqIGltYWdlU2NhbGU7XG4gICAgdmFyIG9mZnNldFRvcCA9IChmb2NhbFBvaW50LnRvcCAvIGltYWdlU2NhbGUgLSBmb2NhbFBvaW50LnRvcCAvIG9sZFNjYWxlKSAqIGltYWdlU2NhbGU7XG5cbiAgICB2YXIgaW1hZ2VMZWZ0ID0gaW1hZ2VTdGF0ZS5sZWZ0IHx8IDA7XG4gICAgdmFyIGltYWdlVG9wID0gaW1hZ2VTdGF0ZS50b3AgfHwgMDtcblxuICAgIHRoaXMubW92ZUltYWdlKGltYWdlTGVmdCArIG9mZnNldExlZnQsIGltYWdlVG9wICsgb2Zmc2V0VG9wKTtcbiAgfSxcblxuICBiaW5kRXZlbnRzOiBmdW5jdGlvbigpIHtcbiAgICB2YXIgY3JvcEJveCA9IHRoaXMucmVmcy5jcm9wQm94O1xuXG4gICAgY3JvcEJveC5hZGRFdmVudExpc3RlbmVyKCd0b3VjaHN0YXJ0JywgdGhpcy5vblRvdWNoU3RhcnQuYmluZCh0aGlzKSk7XG5cbiAgICBjcm9wQm94LmFkZEV2ZW50TGlzdGVuZXIoJ3RvdWNobW92ZScsIHRoaXMub25Ub3VjaE1vdmUuYmluZCh0aGlzKSk7XG5cbiAgICBjcm9wQm94LmFkZEV2ZW50TGlzdGVuZXIoJ3RvdWNoZW5kJywgdGhpcy5vblRvdWNoRW5kLmJpbmQodGhpcykpO1xuICB9LFxuXG4gIGNyZWF0ZUJhc2U2NDogZnVuY3Rpb24gKGNhbGxiYWNrLCB3aWR0aCkge1xuICAgIHZhciBpbWFnZVN0YXRlID0gdGhpcy5pbWFnZVN0YXRlO1xuICAgIHZhciBjcm9wQm94UmVjdCA9IHRoaXMuY3JvcEJveFJlY3Q7XG4gICAgdmFyIHNjYWxlID0gaW1hZ2VTdGF0ZS5zY2FsZTtcblxuICAgIHZhciBjYW52YXNTaXplID0gd2lkdGg7XG5cbiAgICBpZiAoIWNhbnZhc1NpemUpIHtcbiAgICAgIGNhbnZhc1NpemUgPSBjcm9wQm94UmVjdC53aWR0aCAqIDI7XG4gICAgfVxuXG4gICAgdmFyIGltYWdlTGVmdCA9IE1hdGgucm91bmQoKGNyb3BCb3hSZWN0LmxlZnQgLSBpbWFnZVN0YXRlLmxlZnQpIC8gc2NhbGUpO1xuICAgIHZhciBpbWFnZVRvcCA9IE1hdGgucm91bmQoKGNyb3BCb3hSZWN0LnRvcCAtIGltYWdlU3RhdGUudG9wKSAvIHNjYWxlKTtcbiAgICB2YXIgaW1hZ2VTaXplID0gTWF0aC5mbG9vcihjcm9wQm94UmVjdC53aWR0aCAvIHNjYWxlKTtcblxuICAgIHZhciBvcmllbnRhdGlvbiA9IHRoaXMub3JpZW50YXRpb247XG4gICAgdmFyIGltYWdlID0gdGhpcy5yZWZzLmltYWdlO1xuXG4gICAgdmFyIGNyb3BJbWFnZSA9IG5ldyBJbWFnZSgpO1xuICAgIGNyb3BJbWFnZS5zcmMgPSBpbWFnZS5zcmM7XG5cbiAgICBjcm9wSW1hZ2Uub25sb2FkID0gZnVuY3Rpb24oKSB7XG4gICAgICB2YXIgcmVzdWx0Q2FudmFzID0gbG9hZEltYWdlLnNjYWxlKGNyb3BJbWFnZSwge1xuICAgICAgICBjYW52YXM6IHRydWUsXG4gICAgICAgIGxlZnQ6IGltYWdlTGVmdCxcbiAgICAgICAgdG9wOiBpbWFnZVRvcCxcbiAgICAgICAgc291cmNlV2lkdGg6IGltYWdlU2l6ZSxcbiAgICAgICAgc291cmNlSGVpZ2h0OiBpbWFnZVNpemUsXG4gICAgICAgIG9yaWVudGF0aW9uOiBvcmllbnRhdGlvbixcbiAgICAgICAgbWF4V2lkdGg6IGNhbnZhc1NpemUsXG4gICAgICAgIG1heEhlaWdodDogY2FudmFzU2l6ZVxuICAgICAgfSk7XG5cbiAgICAgIHZhciBkYXRhVVJMID0gcmVzdWx0Q2FudmFzLnRvRGF0YVVSTCgpO1xuICAgICAgaWYgKHR5cGVvZiBjYWxsYmFjayA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBjYWxsYmFjayh7XG4gICAgICAgICAgY2FudmFzU2l6ZTogY2FudmFzU2l6ZSxcbiAgICAgICAgICBjYW52YXM6IHJlc3VsdENhbnZhcyxcbiAgICAgICAgICBkYXRhVVJMOiBkYXRhVVJMXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH07XG4gIH0sXG5cbiAgZ2V0Q3JvcHBlZEltYWdlOiBmdW5jdGlvbihjYWxsYmFjaywgd2lkdGgpIHtcbiAgICBpZiAoIXRoaXMuaW1hZ2UpIHJldHVybiBudWxsO1xuXG4gICAgdGhpcy5jcmVhdGVCYXNlNjQoZnVuY3Rpb24ocmVzdWx0KSB7XG4gICAgICB2YXIgY2FudmFzU2l6ZSA9IHJlc3VsdC5jYW52YXNTaXplO1xuICAgICAgdmFyIGNhbnZhcyA9IHJlc3VsdC5jYW52YXM7XG4gICAgICB2YXIgZGF0YVVSTCA9IHJlc3VsdC5kYXRhVVJMO1xuXG4gICAgICBpZiAodHlwZW9mIGNhbGxiYWNrID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIGNhbGxiYWNrKHtcbiAgICAgICAgICBmaWxlOiBjYW52YXMudG9CbG9iID8gY2FudmFzLnRvQmxvYigpIDogZGF0YVVSSXRvQmxvYihkYXRhVVJMKSxcbiAgICAgICAgICBkYXRhVXJsOiBkYXRhVVJMLFxuICAgICAgICAgIG9EYXRhVVJMOiByZXN1bHQub0RhdGFVUkwsXG4gICAgICAgICAgc2l6ZTogY2FudmFzU2l6ZVxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9LCB3aWR0aCk7XG4gIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gQ3JvcHBlcjsiLCJ3aW5kb3cuQ3JvcHBlciA9IHJlcXVpcmUoJy4vY3JvcHBlcicpOyIsIlxudmFyIG9uY2UgPSBmdW5jdGlvbihlbCwgZXZlbnQsIGZuKSB7XG4gIHZhciBsaXN0ZW5lciA9IGZ1bmN0aW9uKCkge1xuICAgIGlmIChmbikge1xuICAgICAgZm4uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICB9XG4gICAgZWwucmVtb3ZlRXZlbnRMaXN0ZW5lcihldmVudCwgbGlzdGVuZXIpO1xuICB9O1xuICBlbC5hZGRFdmVudExpc3RlbmVyKGV2ZW50LCBsaXN0ZW5lcik7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgZGF0YVVSSXRvQmxvYjogZnVuY3Rpb24gKGRhdGFVUkkpIHtcbiAgICB2YXIgYmluYXJ5U3RyaW5nID0gYXRvYihkYXRhVVJJLnNwbGl0KCcsJylbMV0pO1xuICAgIHZhciBhcnJheUJ1ZmZlciA9IG5ldyBBcnJheUJ1ZmZlcihiaW5hcnlTdHJpbmcubGVuZ3RoKTtcbiAgICB2YXIgaW50QXJyYXkgPSBuZXcgVWludDhBcnJheShhcnJheUJ1ZmZlcik7XG5cbiAgICBmb3IgKHZhciBpID0gMCwgaiA9IGJpbmFyeVN0cmluZy5sZW5ndGg7IGkgPCBqOyBpKyspIHtcbiAgICAgIGludEFycmF5W2ldID0gYmluYXJ5U3RyaW5nLmNoYXJDb2RlQXQoaSk7XG4gICAgfVxuXG4gICAgdmFyIGRhdGEgPSBbaW50QXJyYXldO1xuICAgIHZhciB0eXBlID0gJ2ltYWdlL3BuZyc7XG5cbiAgICB2YXIgcmVzdWx0O1xuXG4gICAgdHJ5IHtcbiAgICAgIHJlc3VsdCA9IG5ldyBCbG9iKGRhdGEsIHsgdHlwZTogdHlwZSB9KTtcbiAgICB9IGNhdGNoKGVycm9yKSB7XG4gICAgICAvLyBUeXBlRXJyb3Igb2xkIGNocm9tZSBhbmQgRkZcbiAgICAgIHdpbmRvdy5CbG9iQnVpbGRlciA9IHdpbmRvdy5CbG9iQnVpbGRlciB8fFxuICAgICAgICB3aW5kb3cuV2ViS2l0QmxvYkJ1aWxkZXIgfHxcbiAgICAgICAgd2luZG93Lk1vekJsb2JCdWlsZGVyIHx8XG4gICAgICAgIHdpbmRvdy5NU0Jsb2JCdWlsZGVyO1xuXG4gICAgICBpZihlcnJvci5uYW1lID09ICdUeXBlRXJyb3InICYmIHdpbmRvdy5CbG9iQnVpbGRlcil7XG4gICAgICAgIHZhciBidWlsZGVyID0gbmV3IEJsb2JCdWlsZGVyKCk7XG4gICAgICAgIGJ1aWxkZXIuYXBwZW5kKGFycmF5QnVmZmVyKTtcbiAgICAgICAgcmVzdWx0ID0gYnVpbGRlci5nZXRCbG9iKHR5cGUpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiByZXN1bHQ7XG4gIH0sXG4gIGdldFRvdWNoRGlzdGFuY2U6IGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgdmFyIGZpbmdlciA9IGV2ZW50LnRvdWNoZXNbMF07XG4gICAgdmFyIGZpbmdlcjIgPSBldmVudC50b3VjaGVzWzFdO1xuXG4gICAgdmFyIGMxID0gTWF0aC5hYnMoZmluZ2VyLnBhZ2VYIC0gZmluZ2VyMi5wYWdlWCk7XG4gICAgdmFyIGMyID0gTWF0aC5hYnMoZmluZ2VyLnBhZ2VZIC0gZmluZ2VyMi5wYWdlWSk7XG5cbiAgICByZXR1cm4gTWF0aC5zcXJ0KCBjMSAqIGMxICsgYzIgKiBjMiApO1xuICB9LFxuICB0cmFuc2xhdGU6IGZ1bmN0aW9uKGVsZW1lbnQsIGxlZnQsIHRvcCwgc3BlZWQsIGNhbGxiYWNrKSB7XG4gICAgZWxlbWVudC5zdHlsZS53ZWJraXRUcmFuc2Zvcm0gPSAndHJhbnNsYXRlM2QoJyArIChsZWZ0IHx8IDApICsgJ3B4LCAnICsgKHRvcCB8fCAwKSArICdweCwgMCknO1xuICAgIGlmIChzcGVlZCkge1xuICAgICAgdmFyIGNhbGxlZCA9IGZhbHNlO1xuXG4gICAgICB2YXIgcmVhbENhbGxiYWNrID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIGlmIChjYWxsZWQpIHJldHVybjtcbiAgICAgICAgZWxlbWVudC5zdHlsZS53ZWJraXRUcmFuc2l0aW9uID0gJyc7XG4gICAgICAgIGNhbGxlZCA9IHRydWU7XG4gICAgICAgIGlmIChjYWxsYmFjaykge1xuICAgICAgICAgIGNhbGxiYWNrLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgICBlbGVtZW50LnN0eWxlLndlYmtpdFRyYW5zaXRpb24gPSAnLXdlYmtpdC10cmFuc2Zvcm0gJyArIHNwZWVkICsgJ21zIGN1YmljLWJlemllcigwLjMyNSwgMC43NzAsIDAuMDAwLCAxLjAwMCknO1xuICAgICAgb25jZShlbGVtZW50LCAnd2Via2l0VHJhbnNpdGlvbkVuZCcsIHJlYWxDYWxsYmFjayk7XG4gICAgICBvbmNlKGVsZW1lbnQsICd0cmFuc2l0aW9uZW5kJywgcmVhbENhbGxiYWNrKTtcbiAgICAgIC8vIGZvciBhbmRyb2lkLi4uXG4gICAgICBzZXRUaW1lb3V0KHJlYWxDYWxsYmFjaywgc3BlZWQgKyA1MCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGVsZW1lbnQuc3R5bGUud2Via2l0VHJhbnNpdGlvbiA9ICcnO1xuICAgIH1cbiAgfSxcbiAgdHJhbnNsYXRlRWxlbWVudDogZnVuY3Rpb24oZWxlbWVudCwgbGVmdCwgdG9wKSB7XG4gICAgZWxlbWVudC5zdHlsZS53ZWJraXRUcmFuc2Zvcm0gPSAndHJhbnNsYXRlM2QoJyArIChsZWZ0IHx8IDApICsgJ3B4LCAnICsgKHRvcCB8fCAwKSArICdweCwgMCknO1xuICB9LFxuICBnZXRFbGVtZW50VHJhbnNsYXRlOiBmdW5jdGlvbihlbGVtZW50KSB7XG4gICAgdmFyIHRyYW5zZm9ybSA9IGVsZW1lbnQuc3R5bGUud2Via2l0VHJhbnNmb3JtO1xuICAgIHZhciBtYXRjaGVzID0gL3RyYW5zbGF0ZTNkXFwoKC4qPylcXCkvaWcuZXhlYyh0cmFuc2Zvcm0pO1xuICAgIGlmIChtYXRjaGVzKSB7XG4gICAgICB2YXIgdHJhbnNsYXRlcyA9IG1hdGNoZXNbMV0uc3BsaXQoJywnKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGxlZnQ6IHBhcnNlSW50KHRyYW5zbGF0ZXNbMF0sIDEwKSxcbiAgICAgICAgdG9wOiBwYXJzZUludCh0cmFuc2xhdGVzWzFdLCAxMClcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgIGxlZnQ6IDAsXG4gICAgICB0b3A6IDBcbiAgICB9XG4gIH1cbn07Il19
644 |
--------------------------------------------------------------------------------
/examples/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/orientation_1.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElemeFE/image-cropper-touch/40bdcbb21ea50fbbdadf2c3f1d44dda35dd48686/examples/orientation_1.JPG
--------------------------------------------------------------------------------
/examples/orientation_3.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElemeFE/image-cropper-touch/40bdcbb21ea50fbbdadf2c3f1d44dda35dd48686/examples/orientation_3.JPG
--------------------------------------------------------------------------------
/examples/orientation_6.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElemeFE/image-cropper-touch/40bdcbb21ea50fbbdadf2c3f1d44dda35dd48686/examples/orientation_6.JPG
--------------------------------------------------------------------------------
/examples/orientation_8.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ElemeFE/image-cropper-touch/40bdcbb21ea50fbbdadf2c3f1d44dda35dd48686/examples/orientation_8.JPG
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-cropper-touch",
3 | "version": "0.1.2",
4 | "description": "A image cropper for mobile device.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/ElemeFE/image-cropper-touch.git"
8 | },
9 | "keywords": [
10 | "crop",
11 | "cropper",
12 | "image",
13 | "mobile",
14 | "touch"
15 | ],
16 | "main": "src/index.js",
17 | "author": "long.zhang",
18 | "license": "MIT",
19 | "dependencies": {
20 | "blueimp-load-image": "^1.13.1"
21 | },
22 | "devDependencies": {
23 | "browserify": "^9.0.8",
24 | "watchify": "^3.3.0"
25 | },
26 | "scripts": {
27 | "build": "browserify src/index.js -o dist/cropper.js",
28 | "watch": "watchify src/index.js -o dist/cropper.js -dv"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/cropper.js:
--------------------------------------------------------------------------------
1 | var util = require('./util');
2 |
3 | var translateElement = util.translateElement;
4 | var getElementTranslate = util.getElementTranslate;
5 | var getDistance = util.getTouchDistance;
6 | var translate = util.translate;
7 | var dataURItoBlob = util.dataURItoBlob;
8 | var URLApi = window.createObjectURL && window || window.URL && URL.revokeObjectURL && URL || window.webkitURL && webkitURL;
9 |
10 | var Cropper = function() {
11 | if (!('ontouchstart' in window)) {
12 | throw new Error('this demo should run in mobile device');
13 | }
14 |
15 | this.imageState = {};
16 | };
17 |
18 | Cropper.prototype = {
19 | constructor: Cropper,
20 |
21 | setImage: function(src, file) {
22 | var self = this;
23 | self.imageLoading = true;
24 | self.image = src;
25 |
26 | self.resetSize();
27 |
28 | var url;
29 | if (file) {
30 | url = URLApi.createObjectURL(file);
31 | }
32 |
33 | var image = new Image();
34 |
35 | image.onload = function() {
36 | var selfImage = self.refs.image;
37 |
38 | loadImage.parseMetaData(file, function(data) {
39 | var orientation;
40 | if (data.exif) {
41 | orientation = data.exif[0x0112];
42 | }
43 |
44 | selfImage.src = src;
45 | self.orientation = orientation;
46 |
47 | var originalWidth, originalHeight;
48 |
49 | self.imageState.left = self.imageState.top = 0;
50 |
51 | if ("5678".indexOf(orientation) > -1) {
52 | originalWidth = image.height;
53 | originalHeight = image.width;
54 | } else {
55 | originalWidth = image.width;
56 | originalHeight = image.height;
57 | }
58 |
59 | self.imageState.width = originalWidth;
60 | self.imageState.height = originalHeight;
61 |
62 | self.initScale();
63 |
64 | var minScale = self.scaleRange[0];
65 | var imageWidth = minScale * originalWidth;
66 | var imageHeight = minScale * originalHeight;
67 | selfImage.style.width = imageWidth + 'px';
68 | selfImage.style.height = imageHeight + 'px';
69 |
70 | var imageLeft, imageTop;
71 |
72 | var cropBoxRect = self.cropBoxRect;
73 |
74 | if (originalWidth > originalHeight) {
75 | imageLeft = (cropBoxRect.width - imageWidth) / 2 +cropBoxRect.left;
76 | imageTop = cropBoxRect.top;
77 | } else {
78 | imageLeft = cropBoxRect.left;
79 | imageTop = (cropBoxRect.height - imageHeight) / 2 + cropBoxRect.top;
80 | }
81 |
82 | self.moveImage(imageLeft, imageTop);
83 |
84 | self.imageLoading = false;
85 | });
86 | };
87 | image.src = url || src;
88 | },
89 |
90 | getFocalPoint: function(event) {
91 | var focalPoint = {
92 | left: (event.touches[0].pageX + event.touches[1].pageX) / 2,
93 | top: (event.touches[0].pageY + event.touches[1].pageY) / 2
94 | };
95 |
96 | var imageState = this.imageState;
97 | var cropBoxRect = this.cropBoxRect;
98 |
99 | focalPoint.left -= cropBoxRect.left + imageState.left;
100 | focalPoint.top -= cropBoxRect.top + imageState.top;
101 |
102 | return focalPoint;
103 | },
104 |
105 | render: function(parentNode) {
106 | var element = document.createElement('div');
107 | element.className = 'cropper';
108 |
109 | var coverStart = document.createElement('div');
110 | var coverEnd = document.createElement('div');
111 | var cropBox = document.createElement('div');
112 | var image = document.createElement('img');
113 |
114 | coverStart.className = 'cover cover-start';
115 | coverEnd.className = 'cover cover-end';
116 | cropBox.className = 'crop-box';
117 |
118 | element.appendChild(coverStart);
119 | element.appendChild(coverEnd);
120 | element.appendChild(cropBox);
121 | element.appendChild(image);
122 |
123 | this.refs = {
124 | element: element,
125 | coverStart: coverStart,
126 | coverEnd: coverEnd,
127 | cropBox: cropBox,
128 | image: image
129 | };
130 |
131 | if (parentNode) {
132 | parentNode.appendChild(element);
133 | }
134 |
135 | if (element.offsetHeight > 0) {
136 | this.resetSize();
137 | }
138 |
139 | this.bindEvents();
140 | },
141 |
142 | initScale: function () {
143 | var cropBoxRect = this.cropBoxRect;
144 | var width = this.imageState.width;
145 | var height = this.imageState.height;
146 | var scale, minScale;
147 |
148 | if (width > height) {
149 | scale = this.imageState.scale = cropBoxRect.height / height;
150 | minScale = cropBoxRect.height * 0.8 / height;
151 | } else {
152 | scale = this.imageState.scale = cropBoxRect.width / width;
153 | minScale = cropBoxRect.width * 0.8 / width;
154 | }
155 |
156 | this.scaleRange = [scale, 2];
157 | this.bounceScaleRange = [minScale, 3];
158 | },
159 |
160 | resetSize: function() {
161 | var refs = this.refs;
162 | if (!refs) return;
163 |
164 | var element = refs.element;
165 | var cropBox = refs.cropBox;
166 | var coverStart = refs.coverStart;
167 | var coverEnd = refs.coverEnd;
168 |
169 | var width = element.offsetWidth;
170 | var height = element.offsetHeight;
171 |
172 | if (width > height) {
173 | element.className = 'cropper cropper-horizontal';
174 |
175 | coverStart.style.width = coverEnd.style.width = (width - height) / 2 + 'px';
176 | coverStart.style.height = coverEnd.style.height = '';
177 | cropBox.style.width = cropBox.style.height = height + 'px';
178 | } else {
179 | element.className = 'cropper';
180 |
181 | coverStart.style.height = coverEnd.style.height = (height - width) / 2 + 'px';
182 | coverStart.style.width = coverEnd.style.width = '';
183 | cropBox.style.width = cropBox.style.height = width + 'px';
184 | }
185 |
186 | var elementRect = element.getBoundingClientRect();
187 | var cropBoxRect = cropBox.getBoundingClientRect();
188 |
189 | this.cropBoxRect = {
190 | left: cropBoxRect.left - elementRect.left,
191 | top: cropBoxRect.top - elementRect.top,
192 | width: cropBoxRect.width,
193 | height: cropBoxRect.height
194 | };
195 |
196 | this.initScale();
197 |
198 | this.checkBounce(0);
199 | },
200 |
201 | checkBounce: function (speed) {
202 | var imageState = this.imageState;
203 | var cropBoxRect = this.cropBoxRect;
204 |
205 | var imageWidth = imageState.width;
206 | var imageHeight = imageState.height;
207 | var imageScale = imageState.scale;
208 |
209 | var imageOffset = getElementTranslate(this.refs.image);
210 | var left = imageOffset.left;
211 | var top = imageOffset.top;
212 |
213 | var leftRange = [-imageWidth * imageScale + cropBoxRect.width + cropBoxRect.left, cropBoxRect.left];
214 | var topRange = [-imageHeight * imageScale + cropBoxRect.height + cropBoxRect.top, cropBoxRect.top];
215 |
216 | var overflow = false;
217 |
218 | if (left < leftRange[0]) {
219 | left = leftRange[0];
220 | overflow = true;
221 | } else if (left > leftRange[1]) {
222 | left = leftRange[1];
223 | overflow = true;
224 | }
225 |
226 | if (top < topRange[0]) {
227 | top = topRange[0];
228 | overflow = true;
229 | } else if (top > topRange[1]) {
230 | top = topRange[1];
231 | overflow = true;
232 | }
233 |
234 | if (overflow) {
235 | var self = this;
236 | translate(this.refs.image, left, top, speed === undefined ? 200 : 0, function() {
237 | self.moveImage(left, top);
238 | });
239 | }
240 | },
241 |
242 | moveImage: function(left, top) {
243 | var image = this.refs.image;
244 | translateElement(image, left, top);
245 |
246 | this.imageState.left = left;
247 | this.imageState.top = top;
248 | },
249 |
250 | onTouchStart: function(event) {
251 | this.amplitude = 0;
252 | var image = this.refs.image;
253 |
254 | var fingerCount = event.touches.length;
255 | if (fingerCount) {
256 | var touchEvent = event.touches[0];
257 |
258 | var imageOffset = getElementTranslate(image);
259 |
260 | this.dragState = {
261 | timestamp: Date.now(),
262 | startTouchLeft: touchEvent.pageX,
263 | startTouchTop: touchEvent.pageY,
264 | startLeft: imageOffset.left || 0,
265 | startTop: imageOffset.top || 0
266 | };
267 | }
268 |
269 | if (fingerCount >= 2) {
270 | var zoomState = this.zoomState = {
271 | timestamp: Date.now()
272 | };
273 |
274 | zoomState.startDistance = getDistance(event);
275 | zoomState.focalPoint = this.getFocalPoint(event);
276 | }
277 | },
278 |
279 | onTouchMove: function(event) {
280 | var fingerCount = event.touches.length;
281 |
282 | var touchEvent = event.touches[0];
283 |
284 | var cropBoxRect = this.cropBoxRect;
285 | var image = this.refs.image;
286 |
287 | var imageState = this.imageState;
288 | var imageWidth = imageState.width;
289 | var imageHeight = imageState.height;
290 |
291 | var dragState = this.dragState;
292 | var zoomState = this.zoomState;
293 |
294 | if (fingerCount === 1) {
295 | var leftRange = [ -imageWidth * imageState.scale + cropBoxRect.width, cropBoxRect.left ];
296 | var topRange = [ -imageHeight * imageState.scale + cropBoxRect.height + cropBoxRect.top, cropBoxRect.top ];
297 |
298 | var deltaX = touchEvent.pageX - (dragState.lastLeft || dragState.startTouchLeft);
299 | var deltaY = touchEvent.pageY - (dragState.lastTop || dragState.startTouchTop);
300 |
301 | var imageOffset = getElementTranslate(image);
302 |
303 | var left = imageOffset.left + deltaX;
304 | var top = imageOffset.top + deltaY;
305 |
306 | if (left < leftRange[0] || left > leftRange[1]) {
307 | left -= deltaX / 2;
308 | }
309 |
310 | if (top < topRange [0] || top > topRange[1]) {
311 | top -= deltaY / 2;
312 | }
313 |
314 | this.moveImage(left, top);
315 | } else if (fingerCount >= 2) {
316 | if (!zoomState.timestamp) {
317 | zoomState = {
318 | timestamp: Date.now()
319 | };
320 |
321 | zoomState.startDistance = getDistance(event);
322 | zoomState.focalPoint = this.getFocalPoint(event);
323 |
324 | return;
325 | }
326 |
327 | var newDistance = getDistance(event);
328 | var oldScale = imageState.scale;
329 |
330 | imageState.scale = oldScale * newDistance / (zoomState.lastDistance || zoomState.startDistance);
331 |
332 | var scaleRange = this.scaleRange;
333 | if (imageState.scale < scaleRange[0]) {
334 | imageState.scale = scaleRange[0];
335 | } else if (imageState.scale > scaleRange[1]) {
336 | imageState.scale = scaleRange[1];
337 | }
338 |
339 | this.zoomWithFocal(oldScale);
340 |
341 | zoomState.focalPoint = this.getFocalPoint(event);
342 | zoomState.lastDistance = newDistance;
343 | }
344 |
345 | dragState.lastLeft = touchEvent.pageX;
346 | dragState.lastTop = touchEvent.pageY;
347 | },
348 |
349 | onTouchEnd: function(event) {
350 | var imageState = this.imageState;
351 | var zoomState = this.zoomState;
352 | var dragState = this.dragState;
353 | var amplitude = this.amplitude;
354 | var imageWidth = imageState.width;
355 | var imageHeight = imageState.height;
356 | var cropBoxRect = this.cropBoxRect;
357 |
358 | if (event.touches.length === 0 && dragState.timestamp) {
359 | var self = this;
360 | var duration = Date.now() - dragState.timestamp;
361 |
362 | if (duration > 300) {
363 | self.checkBounce();
364 | } else {
365 | var target;
366 |
367 | var top = imageState.top;
368 | var left = imageState.left;
369 |
370 | var momentumVertical = false;
371 |
372 | var timeConstant = 160;
373 |
374 | var autoScroll = function () {
375 | var elapsed, delta;
376 |
377 | if (amplitude) {
378 | elapsed = Date.now() - timestamp;
379 | delta = -amplitude * Math.exp(-elapsed / timeConstant);
380 | if (delta > 0.5 || delta < -0.5) {
381 | if (momentumVertical) {
382 | self.moveImage(left, target + delta);
383 | } else {
384 | self.moveImage(target + delta, top);
385 | }
386 |
387 | requestAnimationFrame(autoScroll);
388 | } else {
389 | var currentLeft;
390 | var currentTop;
391 |
392 | if (momentumVertical) {
393 | currentLeft = left;
394 | currentTop = target;
395 | } else {
396 | currentLeft = target;
397 | currentTop = top;
398 | }
399 |
400 | self.moveImage(currentLeft, currentTop);
401 | self.checkBounce();
402 | }
403 | }
404 | };
405 |
406 | var velocity;
407 |
408 | var deltaX = event.changedTouches[0].pageX - dragState.startTouchLeft;
409 | var deltaY = event.changedTouches[0].pageY - dragState.startTouchTop;
410 |
411 | if (Math.abs(deltaX) > Math.abs(deltaY)) {
412 | velocity = deltaX / duration;
413 | } else {
414 | momentumVertical = true;
415 | velocity = deltaY / duration;
416 | }
417 |
418 | amplitude = 80 * velocity;
419 |
420 | var range;
421 |
422 | if (momentumVertical) {
423 | target = Math.round(imageState.top + amplitude);
424 | range = [-imageHeight * imageState.scale + cropBoxRect.height / 2 + cropBoxRect.top, cropBoxRect.top + cropBoxRect.height / 2];
425 | } else {
426 | target = Math.round(imageState.left + amplitude);
427 | range = [-imageWidth * imageState.scale + cropBoxRect.width / 2, cropBoxRect.left + cropBoxRect.width / 2];
428 | }
429 |
430 | if (target < range[0]) {
431 | target = range[0];
432 | amplitude /= 2;
433 | } else if (target > range[1]) {
434 | target = range[1];
435 | amplitude /= 2;
436 | }
437 |
438 | var timestamp = Date.now();
439 | requestAnimationFrame(autoScroll);
440 | }
441 |
442 | this.dragState = {};
443 | } else if (zoomState.timestamp) {
444 | this.checkBounce();
445 |
446 | this.zoomState = {};
447 | }
448 | },
449 |
450 | zoomWithFocal: function(oldScale) {
451 | var image = this.refs.image;
452 | var imageState = this.imageState;
453 | var imageScale = imageState.scale;
454 |
455 | image.style.width = imageState.width * imageScale + 'px';
456 | image.style.height = imageState.height * imageScale + 'px';
457 |
458 | var focalPoint = this.zoomState.focalPoint;
459 |
460 | var offsetLeft = (focalPoint.left / imageScale - focalPoint.left / oldScale) * imageScale;
461 | var offsetTop = (focalPoint.top / imageScale - focalPoint.top / oldScale) * imageScale;
462 |
463 | var imageLeft = imageState.left || 0;
464 | var imageTop = imageState.top || 0;
465 |
466 | this.moveImage(imageLeft + offsetLeft, imageTop + offsetTop);
467 | },
468 |
469 | bindEvents: function() {
470 | var cropBox = this.refs.cropBox;
471 |
472 | cropBox.addEventListener('touchstart', this.onTouchStart.bind(this));
473 |
474 | cropBox.addEventListener('touchmove', this.onTouchMove.bind(this));
475 |
476 | cropBox.addEventListener('touchend', this.onTouchEnd.bind(this));
477 | },
478 |
479 | createBase64: function (callback, width) {
480 | var imageState = this.imageState;
481 | var cropBoxRect = this.cropBoxRect;
482 | var scale = imageState.scale;
483 |
484 | var canvasSize = width;
485 |
486 | if (!canvasSize) {
487 | canvasSize = cropBoxRect.width * 2;
488 | }
489 |
490 | var imageLeft = Math.round((cropBoxRect.left - imageState.left) / scale);
491 | var imageTop = Math.round((cropBoxRect.top - imageState.top) / scale);
492 | var imageSize = Math.floor(cropBoxRect.width / scale);
493 |
494 | var orientation = this.orientation;
495 | var image = this.refs.image;
496 |
497 | var cropImage = new Image();
498 | cropImage.src = image.src;
499 |
500 | cropImage.onload = function() {
501 | var resultCanvas = loadImage.scale(cropImage, {
502 | canvas: true,
503 | left: imageLeft,
504 | top: imageTop,
505 | sourceWidth: imageSize,
506 | sourceHeight: imageSize,
507 | orientation: orientation,
508 | maxWidth: canvasSize,
509 | maxHeight: canvasSize
510 | });
511 |
512 | var dataURL = resultCanvas.toDataURL();
513 | if (typeof callback === 'function') {
514 | callback({
515 | canvasSize: canvasSize,
516 | canvas: resultCanvas,
517 | dataURL: dataURL
518 | });
519 | }
520 | };
521 | },
522 |
523 | getCroppedImage: function(callback, width) {
524 | if (!this.image) return null;
525 |
526 | this.createBase64(function(result) {
527 | var canvasSize = result.canvasSize;
528 | var canvas = result.canvas;
529 | var dataURL = result.dataURL;
530 |
531 | if (typeof callback === 'function') {
532 | callback({
533 | file: canvas.toBlob ? canvas.toBlob() : dataURItoBlob(dataURL),
534 | dataUrl: dataURL,
535 | oDataURL: result.oDataURL,
536 | size: canvasSize
537 | });
538 | }
539 | }, width);
540 | }
541 | };
542 |
543 | module.exports = Cropper;
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | window.Cropper = require('./cropper');
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 |
2 | var once = function(el, event, fn) {
3 | var listener = function() {
4 | if (fn) {
5 | fn.apply(this, arguments);
6 | }
7 | el.removeEventListener(event, listener);
8 | };
9 | el.addEventListener(event, listener);
10 | };
11 |
12 | module.exports = {
13 | dataURItoBlob: function (dataURI) {
14 | var binaryString = atob(dataURI.split(',')[1]);
15 | var arrayBuffer = new ArrayBuffer(binaryString.length);
16 | var intArray = new Uint8Array(arrayBuffer);
17 |
18 | for (var i = 0, j = binaryString.length; i < j; i++) {
19 | intArray[i] = binaryString.charCodeAt(i);
20 | }
21 |
22 | var data = [intArray];
23 | var type = 'image/png';
24 |
25 | var result;
26 |
27 | try {
28 | result = new Blob(data, { type: type });
29 | } catch(error) {
30 | // TypeError old chrome and FF
31 | window.BlobBuilder = window.BlobBuilder ||
32 | window.WebKitBlobBuilder ||
33 | window.MozBlobBuilder ||
34 | window.MSBlobBuilder;
35 |
36 | if(error.name == 'TypeError' && window.BlobBuilder){
37 | var builder = new BlobBuilder();
38 | builder.append(arrayBuffer);
39 | result = builder.getBlob(type);
40 | }
41 | }
42 |
43 | return result;
44 | },
45 | getTouchDistance: function(event) {
46 | var finger = event.touches[0];
47 | var finger2 = event.touches[1];
48 |
49 | var c1 = Math.abs(finger.pageX - finger2.pageX);
50 | var c2 = Math.abs(finger.pageY - finger2.pageY);
51 |
52 | return Math.sqrt( c1 * c1 + c2 * c2 );
53 | },
54 | translate: function(element, left, top, speed, callback) {
55 | element.style.webkitTransform = 'translate3d(' + (left || 0) + 'px, ' + (top || 0) + 'px, 0)';
56 | if (speed) {
57 | var called = false;
58 |
59 | var realCallback = function() {
60 | if (called) return;
61 | element.style.webkitTransition = '';
62 | called = true;
63 | if (callback) {
64 | callback.apply(this, arguments);
65 | }
66 | };
67 | element.style.webkitTransition = '-webkit-transform ' + speed + 'ms cubic-bezier(0.325, 0.770, 0.000, 1.000)';
68 | once(element, 'webkitTransitionEnd', realCallback);
69 | once(element, 'transitionend', realCallback);
70 | // for android...
71 | setTimeout(realCallback, speed + 50);
72 | } else {
73 | element.style.webkitTransition = '';
74 | }
75 | },
76 | translateElement: function(element, left, top) {
77 | element.style.webkitTransform = 'translate3d(' + (left || 0) + 'px, ' + (top || 0) + 'px, 0)';
78 | },
79 | getElementTranslate: function(element) {
80 | var transform = element.style.webkitTransform;
81 | var matches = /translate3d\((.*?)\)/ig.exec(transform);
82 | if (matches) {
83 | var translates = matches[1].split(',');
84 | return {
85 | left: parseInt(translates[0], 10),
86 | top: parseInt(translates[1], 10)
87 | }
88 | }
89 | return {
90 | left: 0,
91 | top: 0
92 | }
93 | }
94 | };
--------------------------------------------------------------------------------