├── .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 | }; --------------------------------------------------------------------------------