croppie.js 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625
  1. /*************************
  2. * Croppie
  3. * Copyright 2019
  4. * Foliotek
  5. * Version: 2.6.4
  6. *************************/
  7. (function (root, factory) {
  8. if (typeof define === 'function' && define.amd) {
  9. // AMD. Register as an anonymous module.
  10. define(factory);
  11. } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
  12. // CommonJS
  13. module.exports = factory();
  14. } else {
  15. // Browser globals
  16. root.Croppie = factory();
  17. }
  18. }(typeof self !== 'undefined' ? self : this, function () {
  19. /* Polyfills */
  20. if (typeof Promise !== 'function') {
  21. /*! promise-polyfill 3.1.0 */
  22. !function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!==typeof this)throw new TypeError("Promises must be constructed via new");if("function"!==typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void k(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"===typeof a||"function"===typeof a)){var c=a.then;if("function"===typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"===typeof a?a:null,this.onRejected="function"===typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=setTimeout,k="function"===typeof setImmediate&&setImmediate||function(a){j(a,1)},l=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&l(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"===typeof g||"function"===typeof g)){var h=g.then;if("function"===typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;f<a.length;f++)d(f,a[f])})},c.resolve=function(a){return a&&"object"===typeof a&&a.constructor===c?a:new c(function(b){b(a)})},c.reject=function(a){return new c(function(b,c){c(a)})},c.race=function(a){return new c(function(b,c){for(var d=0,e=a.length;e>d;d++)a[d].then(b,c)})},c._setImmediateFn=function(a){k=a},"undefined"!==typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)}(this);
  23. }
  24. if ( typeof window.CustomEvent !== "function" ) {
  25. (function(){
  26. function CustomEvent ( event, params ) {
  27. params = params || { bubbles: false, cancelable: false, detail: undefined };
  28. var evt = document.createEvent( 'CustomEvent' );
  29. evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
  30. return evt;
  31. }
  32. CustomEvent.prototype = window.Event.prototype;
  33. window.CustomEvent = CustomEvent;
  34. }());
  35. }
  36. if (!HTMLCanvasElement.prototype.toBlob) {
  37. Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
  38. value: function (callback, type, quality) {
  39. var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
  40. len = binStr.length,
  41. arr = new Uint8Array(len);
  42. for (var i=0; i<len; i++ ) {
  43. arr[i] = binStr.charCodeAt(i);
  44. }
  45. callback( new Blob( [arr], {type: type || 'image/png'} ) );
  46. }
  47. });
  48. }
  49. /* End Polyfills */
  50. var cssPrefixes = ['Webkit', 'Moz', 'ms'],
  51. emptyStyles = document.createElement('div').style,
  52. EXIF_NORM = [1,8,3,6],
  53. EXIF_FLIP = [2,7,4,5],
  54. CSS_TRANS_ORG,
  55. CSS_TRANSFORM,
  56. CSS_USERSELECT;
  57. function vendorPrefix(prop) {
  58. if (prop in emptyStyles) {
  59. return prop;
  60. }
  61. var capProp = prop[0].toUpperCase() + prop.slice(1),
  62. i = cssPrefixes.length;
  63. while (i--) {
  64. prop = cssPrefixes[i] + capProp;
  65. if (prop in emptyStyles) {
  66. return prop;
  67. }
  68. }
  69. }
  70. CSS_TRANSFORM = vendorPrefix('transform');
  71. CSS_TRANS_ORG = vendorPrefix('transformOrigin');
  72. CSS_USERSELECT = vendorPrefix('userSelect');
  73. function getExifOffset(ornt, rotate) {
  74. var arr = EXIF_NORM.indexOf(ornt) > -1 ? EXIF_NORM : EXIF_FLIP,
  75. index = arr.indexOf(ornt),
  76. offset = (rotate / 90) % arr.length;// 180 = 2%4 = 2 shift exif by 2 indexes
  77. return arr[(arr.length + index + (offset % arr.length)) % arr.length];
  78. }
  79. // Credits to : Andrew Dupont - http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
  80. function deepExtend(destination, source) {
  81. destination = destination || {};
  82. for (var property in source) {
  83. if (source[property] && source[property].constructor && source[property].constructor === Object) {
  84. destination[property] = destination[property] || {};
  85. deepExtend(destination[property], source[property]);
  86. } else {
  87. destination[property] = source[property];
  88. }
  89. }
  90. return destination;
  91. }
  92. function clone(object) {
  93. return deepExtend({}, object);
  94. }
  95. function debounce(func, wait, immediate) {
  96. var timeout;
  97. return function () {
  98. var context = this, args = arguments;
  99. var later = function () {
  100. timeout = null;
  101. if (!immediate) func.apply(context, args);
  102. };
  103. var callNow = immediate && !timeout;
  104. clearTimeout(timeout);
  105. timeout = setTimeout(later, wait);
  106. if (callNow) func.apply(context, args);
  107. };
  108. }
  109. function dispatchChange(element) {
  110. if ("createEvent" in document) {
  111. var evt = document.createEvent("HTMLEvents");
  112. evt.initEvent("change", false, true);
  113. element.dispatchEvent(evt);
  114. }
  115. else {
  116. element.fireEvent("onchange");
  117. }
  118. }
  119. //http://jsperf.com/vanilla-css
  120. function css(el, styles, val) {
  121. if (typeof (styles) === 'string') {
  122. var tmp = styles;
  123. styles = {};
  124. styles[tmp] = val;
  125. }
  126. for (var prop in styles) {
  127. el.style[prop] = styles[prop];
  128. }
  129. }
  130. function addClass(el, c) {
  131. if (el.classList) {
  132. el.classList.add(c);
  133. }
  134. else {
  135. el.className += ' ' + c;
  136. }
  137. }
  138. function removeClass(el, c) {
  139. if (el.classList) {
  140. el.classList.remove(c);
  141. }
  142. else {
  143. el.className = el.className.replace(c, '');
  144. }
  145. }
  146. function setAttributes(el, attrs) {
  147. for (var key in attrs) {
  148. el.setAttribute(key, attrs[key]);
  149. }
  150. }
  151. function num(v) {
  152. return parseInt(v, 10);
  153. }
  154. /* Utilities */
  155. function loadImage(src, doExif) {
  156. var img = new Image();
  157. img.style.opacity = '0';
  158. return new Promise(function (resolve, reject) {
  159. function _resolve() {
  160. img.style.opacity = '1';
  161. setTimeout(function () {
  162. resolve(img);
  163. }, 1);
  164. }
  165. img.removeAttribute('crossOrigin');
  166. if (src.match(/^https?:\/\/|^\/\//)) {
  167. img.setAttribute('crossOrigin', 'anonymous');
  168. }
  169. img.onload = function () {
  170. if (doExif) {
  171. EXIF.getData(img, function () {
  172. _resolve();
  173. });
  174. }
  175. else {
  176. _resolve();
  177. }
  178. };
  179. img.onerror = function (ev) {
  180. img.style.opacity = 1;
  181. setTimeout(function () {
  182. reject(ev);
  183. }, 1);
  184. };
  185. img.src = src;
  186. });
  187. }
  188. function naturalImageDimensions(img, ornt) {
  189. var w = img.naturalWidth;
  190. var h = img.naturalHeight;
  191. var orient = ornt || getExifOrientation(img);
  192. if (orient && orient >= 5) {
  193. var x= w;
  194. w = h;
  195. h = x;
  196. }
  197. return { width: w, height: h };
  198. }
  199. /* CSS Transform Prototype */
  200. var TRANSLATE_OPTS = {
  201. 'translate3d': {
  202. suffix: ', 0px'
  203. },
  204. 'translate': {
  205. suffix: ''
  206. }
  207. };
  208. var Transform = function (x, y, scale) {
  209. this.x = parseFloat(x);
  210. this.y = parseFloat(y);
  211. this.scale = parseFloat(scale);
  212. };
  213. Transform.parse = function (v) {
  214. if (v.style) {
  215. return Transform.parse(v.style[CSS_TRANSFORM]);
  216. }
  217. else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) {
  218. return Transform.fromMatrix(v);
  219. }
  220. else {
  221. return Transform.fromString(v);
  222. }
  223. };
  224. Transform.fromMatrix = function (v) {
  225. var vals = v.substring(7).split(',');
  226. if (!vals.length || v === 'none') {
  227. vals = [1, 0, 0, 1, 0, 0];
  228. }
  229. return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0]));
  230. };
  231. Transform.fromString = function (v) {
  232. var values = v.split(') '),
  233. translate = values[0].substring(Croppie.globals.translate.length + 1).split(','),
  234. scale = values.length > 1 ? values[1].substring(6) : 1,
  235. x = translate.length > 1 ? translate[0] : 0,
  236. y = translate.length > 1 ? translate[1] : 0;
  237. return new Transform(x, y, scale);
  238. };
  239. Transform.prototype.toString = function () {
  240. var suffix = TRANSLATE_OPTS[Croppie.globals.translate].suffix || '';
  241. return Croppie.globals.translate + '(' + this.x + 'px, ' + this.y + 'px' + suffix + ') scale(' + this.scale + ')';
  242. };
  243. var TransformOrigin = function (el) {
  244. if (!el || !el.style[CSS_TRANS_ORG]) {
  245. this.x = 0;
  246. this.y = 0;
  247. return;
  248. }
  249. var css = el.style[CSS_TRANS_ORG].split(' ');
  250. this.x = parseFloat(css[0]);
  251. this.y = parseFloat(css[1]);
  252. };
  253. TransformOrigin.prototype.toString = function () {
  254. return this.x + 'px ' + this.y + 'px';
  255. };
  256. function getExifOrientation (img) {
  257. return img.exifdata && img.exifdata.Orientation ? num(img.exifdata.Orientation) : 1;
  258. }
  259. function drawCanvas(canvas, img, orientation) {
  260. var width = img.width,
  261. height = img.height,
  262. ctx = canvas.getContext('2d');
  263. canvas.width = img.width;
  264. canvas.height = img.height;
  265. ctx.save();
  266. switch (orientation) {
  267. case 2:
  268. ctx.translate(width, 0);
  269. ctx.scale(-1, 1);
  270. break;
  271. case 3:
  272. ctx.translate(width, height);
  273. ctx.rotate(180*Math.PI/180);
  274. break;
  275. case 4:
  276. ctx.translate(0, height);
  277. ctx.scale(1, -1);
  278. break;
  279. case 5:
  280. canvas.width = height;
  281. canvas.height = width;
  282. ctx.rotate(90*Math.PI/180);
  283. ctx.scale(1, -1);
  284. break;
  285. case 6:
  286. canvas.width = height;
  287. canvas.height = width;
  288. ctx.rotate(90*Math.PI/180);
  289. ctx.translate(0, -height);
  290. break;
  291. case 7:
  292. canvas.width = height;
  293. canvas.height = width;
  294. ctx.rotate(-90*Math.PI/180);
  295. ctx.translate(-width, height);
  296. ctx.scale(1, -1);
  297. break;
  298. case 8:
  299. canvas.width = height;
  300. canvas.height = width;
  301. ctx.translate(0, width);
  302. ctx.rotate(-90*Math.PI/180);
  303. break;
  304. }
  305. ctx.drawImage(img, 0,0, width, height);
  306. ctx.restore();
  307. }
  308. /* Private Methods */
  309. function _create() {
  310. var self = this,
  311. contClass = 'croppie-container',
  312. customViewportClass = self.options.viewport.type ? 'cr-vp-' + self.options.viewport.type : null,
  313. boundary, img, viewport, overlay, bw, bh;
  314. self.options.useCanvas = self.options.enableOrientation || _hasExif.call(self);
  315. // Properties on class
  316. self.data = {};
  317. self.elements = {};
  318. boundary = self.elements.boundary = document.createElement('div');
  319. viewport = self.elements.viewport = document.createElement('div');
  320. img = self.elements.img = document.createElement('img');
  321. overlay = self.elements.overlay = document.createElement('div');
  322. if (self.options.useCanvas) {
  323. self.elements.canvas = document.createElement('canvas');
  324. self.elements.preview = self.elements.canvas;
  325. }
  326. else {
  327. self.elements.preview = img;
  328. }
  329. addClass(boundary, 'cr-boundary');
  330. boundary.setAttribute('aria-dropeffect', 'none');
  331. bw = self.options.boundary.width;
  332. bh = self.options.boundary.height;
  333. css(boundary, {
  334. width: (bw + (isNaN(bw) ? '' : 'px')),
  335. height: (bh + (isNaN(bh) ? '' : 'px'))
  336. });
  337. addClass(viewport, 'cr-viewport');
  338. if (customViewportClass) {
  339. addClass(viewport, customViewportClass);
  340. }
  341. css(viewport, {
  342. width: self.options.viewport.width + 'px',
  343. height: self.options.viewport.height + 'px'
  344. });
  345. viewport.setAttribute('tabindex', 0);
  346. addClass(self.elements.preview, 'cr-image');
  347. setAttributes(self.elements.preview, { 'alt': 'preview', 'aria-grabbed': 'false' });
  348. addClass(overlay, 'cr-overlay');
  349. self.element.appendChild(boundary);
  350. boundary.appendChild(self.elements.preview);
  351. boundary.appendChild(viewport);
  352. boundary.appendChild(overlay);
  353. addClass(self.element, contClass);
  354. if (self.options.customClass) {
  355. addClass(self.element, self.options.customClass);
  356. }
  357. _initDraggable.call(this);
  358. if (self.options.enableZoom) {
  359. _initializeZoom.call(self);
  360. }
  361. // if (self.options.enableOrientation) {
  362. // _initRotationControls.call(self);
  363. // }
  364. if (self.options.enableResize) {
  365. _initializeResize.call(self);
  366. }
  367. }
  368. // function _initRotationControls () {
  369. // var self = this,
  370. // wrap, btnLeft, btnRight, iLeft, iRight;
  371. // wrap = document.createElement('div');
  372. // self.elements.orientationBtnLeft = btnLeft = document.createElement('button');
  373. // self.elements.orientationBtnRight = btnRight = document.createElement('button');
  374. // wrap.appendChild(btnLeft);
  375. // wrap.appendChild(btnRight);
  376. // iLeft = document.createElement('i');
  377. // iRight = document.createElement('i');
  378. // btnLeft.appendChild(iLeft);
  379. // btnRight.appendChild(iRight);
  380. // addClass(wrap, 'cr-rotate-controls');
  381. // addClass(btnLeft, 'cr-rotate-l');
  382. // addClass(btnRight, 'cr-rotate-r');
  383. // self.elements.boundary.appendChild(wrap);
  384. // btnLeft.addEventListener('click', function () {
  385. // self.rotate(-90);
  386. // });
  387. // btnRight.addEventListener('click', function () {
  388. // self.rotate(90);
  389. // });
  390. // }
  391. function _hasExif() {
  392. return this.options.enableExif && window.EXIF;
  393. }
  394. function _initializeResize () {
  395. var self = this;
  396. var wrap = document.createElement('div');
  397. var isDragging = false;
  398. var direction;
  399. var originalX;
  400. var originalY;
  401. var minSize = 50;
  402. var maxWidth;
  403. var maxHeight;
  404. var vr;
  405. var hr;
  406. addClass(wrap, 'cr-resizer');
  407. css(wrap, {
  408. width: this.options.viewport.width + 'px',
  409. height: this.options.viewport.height + 'px'
  410. });
  411. if (this.options.resizeControls.height) {
  412. vr = document.createElement('div');
  413. addClass(vr, 'cr-resizer-vertical');
  414. wrap.appendChild(vr);
  415. }
  416. if (this.options.resizeControls.width) {
  417. hr = document.createElement('div');
  418. addClass(hr, 'cr-resizer-horisontal');
  419. wrap.appendChild(hr);
  420. }
  421. function mouseDown(ev) {
  422. if (ev.button !== undefined && ev.button !== 0) return;
  423. ev.preventDefault();
  424. if (isDragging) {
  425. return;
  426. }
  427. var overlayRect = self.elements.overlay.getBoundingClientRect();
  428. isDragging = true;
  429. originalX = ev.pageX;
  430. originalY = ev.pageY;
  431. direction = ev.currentTarget.className.indexOf('vertical') !== -1 ? 'v' : 'h';
  432. maxWidth = overlayRect.width;
  433. maxHeight = overlayRect.height;
  434. if (ev.touches) {
  435. var touches = ev.touches[0];
  436. originalX = touches.pageX;
  437. originalY = touches.pageY;
  438. }
  439. window.addEventListener('mousemove', mouseMove);
  440. window.addEventListener('touchmove', mouseMove);
  441. window.addEventListener('mouseup', mouseUp);
  442. window.addEventListener('touchend', mouseUp);
  443. document.body.style[CSS_USERSELECT] = 'none';
  444. }
  445. function mouseMove(ev) {
  446. var pageX = ev.pageX;
  447. var pageY = ev.pageY;
  448. ev.preventDefault();
  449. if (ev.touches) {
  450. var touches = ev.touches[0];
  451. pageX = touches.pageX;
  452. pageY = touches.pageY;
  453. }
  454. var deltaX = pageX - originalX;
  455. var deltaY = pageY - originalY;
  456. var newHeight = self.options.viewport.height + deltaY;
  457. var newWidth = self.options.viewport.width + deltaX;
  458. if (direction === 'v' && newHeight >= minSize && newHeight <= maxHeight) {
  459. css(wrap, {
  460. height: newHeight + 'px'
  461. });
  462. self.options.boundary.height += deltaY;
  463. css(self.elements.boundary, {
  464. height: self.options.boundary.height + 'px'
  465. });
  466. self.options.viewport.height += deltaY;
  467. css(self.elements.viewport, {
  468. height: self.options.viewport.height + 'px'
  469. });
  470. }
  471. else if (direction === 'h' && newWidth >= minSize && newWidth <= maxWidth) {
  472. css(wrap, {
  473. width: newWidth + 'px'
  474. });
  475. self.options.boundary.width += deltaX;
  476. css(self.elements.boundary, {
  477. width: self.options.boundary.width + 'px'
  478. });
  479. self.options.viewport.width += deltaX;
  480. css(self.elements.viewport, {
  481. width: self.options.viewport.width + 'px'
  482. });
  483. }
  484. _updateOverlay.call(self);
  485. _updateZoomLimits.call(self);
  486. _updateCenterPoint.call(self);
  487. _triggerUpdate.call(self);
  488. originalY = pageY;
  489. originalX = pageX;
  490. }
  491. function mouseUp() {
  492. isDragging = false;
  493. window.removeEventListener('mousemove', mouseMove);
  494. window.removeEventListener('touchmove', mouseMove);
  495. window.removeEventListener('mouseup', mouseUp);
  496. window.removeEventListener('touchend', mouseUp);
  497. document.body.style[CSS_USERSELECT] = '';
  498. }
  499. if (vr) {
  500. vr.addEventListener('mousedown', mouseDown);
  501. vr.addEventListener('touchstart', mouseDown);
  502. }
  503. if (hr) {
  504. hr.addEventListener('mousedown', mouseDown);
  505. hr.addEventListener('touchstart', mouseDown);
  506. }
  507. this.elements.boundary.appendChild(wrap);
  508. }
  509. function _setZoomerVal(v) {
  510. if (this.options.enableZoom) {
  511. var z = this.elements.zoomer,
  512. val = fix(v, 4);
  513. z.value = Math.max(parseFloat(z.min), Math.min(parseFloat(z.max), val)).toString();
  514. }
  515. }
  516. function _initializeZoom() {
  517. var self = this,
  518. wrap = self.elements.zoomerWrap = document.createElement('div'),
  519. zoomer = self.elements.zoomer = document.createElement('input');
  520. addClass(wrap, 'cr-slider-wrap');
  521. addClass(zoomer, 'cr-slider');
  522. zoomer.type = 'range';
  523. zoomer.step = '0.0001';
  524. zoomer.value = '1';
  525. zoomer.style.display = self.options.showZoomer ? '' : 'none';
  526. zoomer.setAttribute('aria-label', 'zoom');
  527. self.element.appendChild(wrap);
  528. wrap.appendChild(zoomer);
  529. self._currentZoom = 1;
  530. function change() {
  531. _onZoom.call(self, {
  532. value: parseFloat(zoomer.value),
  533. origin: new TransformOrigin(self.elements.preview),
  534. viewportRect: self.elements.viewport.getBoundingClientRect(),
  535. transform: Transform.parse(self.elements.preview)
  536. });
  537. }
  538. function scroll(ev) {
  539. var delta, targetZoom;
  540. if(self.options.mouseWheelZoom === 'ctrl' && ev.ctrlKey !== true){
  541. return 0;
  542. } else if (ev.wheelDelta) {
  543. delta = ev.wheelDelta / 1200; //wheelDelta min: -120 max: 120 // max x 10 x 2
  544. } else if (ev.deltaY) {
  545. delta = ev.deltaY / 1060; //deltaY min: -53 max: 53 // max x 10 x 2
  546. } else if (ev.detail) {
  547. delta = ev.detail / -60; //delta min: -3 max: 3 // max x 10 x 2
  548. } else {
  549. delta = 0;
  550. }
  551. targetZoom = self._currentZoom + (delta * self._currentZoom);
  552. ev.preventDefault();
  553. _setZoomerVal.call(self, targetZoom);
  554. change.call(self);
  555. }
  556. self.elements.zoomer.addEventListener('input', change);// this is being fired twice on keypress
  557. self.elements.zoomer.addEventListener('change', change);
  558. if (self.options.mouseWheelZoom) {
  559. self.elements.boundary.addEventListener('mousewheel', scroll);
  560. self.elements.boundary.addEventListener('DOMMouseScroll', scroll);
  561. }
  562. }
  563. function _onZoom(ui) {
  564. var self = this,
  565. transform = ui ? ui.transform : Transform.parse(self.elements.preview),
  566. vpRect = ui ? ui.viewportRect : self.elements.viewport.getBoundingClientRect(),
  567. origin = ui ? ui.origin : new TransformOrigin(self.elements.preview);
  568. function applyCss() {
  569. var transCss = {};
  570. transCss[CSS_TRANSFORM] = transform.toString();
  571. transCss[CSS_TRANS_ORG] = origin.toString();
  572. css(self.elements.preview, transCss);
  573. }
  574. self._currentZoom = ui ? ui.value : self._currentZoom;
  575. transform.scale = self._currentZoom;
  576. self.elements.zoomer.setAttribute('aria-valuenow', self._currentZoom);
  577. applyCss();
  578. if (self.options.enforceBoundary) {
  579. var boundaries = _getVirtualBoundaries.call(self, vpRect),
  580. transBoundaries = boundaries.translate,
  581. oBoundaries = boundaries.origin;
  582. if (transform.x >= transBoundaries.maxX) {
  583. origin.x = oBoundaries.minX;
  584. transform.x = transBoundaries.maxX;
  585. }
  586. if (transform.x <= transBoundaries.minX) {
  587. origin.x = oBoundaries.maxX;
  588. transform.x = transBoundaries.minX;
  589. }
  590. if (transform.y >= transBoundaries.maxY) {
  591. origin.y = oBoundaries.minY;
  592. transform.y = transBoundaries.maxY;
  593. }
  594. if (transform.y <= transBoundaries.minY) {
  595. origin.y = oBoundaries.maxY;
  596. transform.y = transBoundaries.minY;
  597. }
  598. }
  599. applyCss();
  600. _debouncedOverlay.call(self);
  601. _triggerUpdate.call(self);
  602. }
  603. function _getVirtualBoundaries(viewport) {
  604. var self = this,
  605. scale = self._currentZoom,
  606. vpWidth = viewport.width,
  607. vpHeight = viewport.height,
  608. centerFromBoundaryX = self.elements.boundary.clientWidth / 2,
  609. centerFromBoundaryY = self.elements.boundary.clientHeight / 2,
  610. imgRect = self.elements.preview.getBoundingClientRect(),
  611. curImgWidth = imgRect.width,
  612. curImgHeight = imgRect.height,
  613. halfWidth = vpWidth / 2,
  614. halfHeight = vpHeight / 2;
  615. var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1;
  616. var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale)));
  617. var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1;
  618. var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale)));
  619. var originMinX = (1 / scale) * halfWidth;
  620. var originMaxX = (curImgWidth * (1 / scale)) - originMinX;
  621. var originMinY = (1 / scale) * halfHeight;
  622. var originMaxY = (curImgHeight * (1 / scale)) - originMinY;
  623. return {
  624. translate: {
  625. maxX: maxX,
  626. minX: minX,
  627. maxY: maxY,
  628. minY: minY
  629. },
  630. origin: {
  631. maxX: originMaxX,
  632. minX: originMinX,
  633. maxY: originMaxY,
  634. minY: originMinY
  635. }
  636. };
  637. }
  638. function _updateCenterPoint(rotate) {
  639. var self = this,
  640. scale = self._currentZoom,
  641. data = self.elements.preview.getBoundingClientRect(),
  642. vpData = self.elements.viewport.getBoundingClientRect(),
  643. transform = Transform.parse(self.elements.preview.style[CSS_TRANSFORM]),
  644. pc = new TransformOrigin(self.elements.preview),
  645. top = (vpData.top - data.top) + (vpData.height / 2),
  646. left = (vpData.left - data.left) + (vpData.width / 2),
  647. center = {},
  648. adj = {};
  649. if (rotate) {
  650. var cx = pc.x;
  651. var cy = pc.y;
  652. var tx = transform.x;
  653. var ty = transform.y;
  654. center.y = cx;
  655. center.x = cy;
  656. transform.y = tx;
  657. transform.x = ty;
  658. }
  659. else {
  660. center.y = top / scale;
  661. center.x = left / scale;
  662. adj.y = (center.y - pc.y) * (1 - scale);
  663. adj.x = (center.x - pc.x) * (1 - scale);
  664. transform.x -= adj.x;
  665. transform.y -= adj.y;
  666. }
  667. var newCss = {};
  668. newCss[CSS_TRANS_ORG] = center.x + 'px ' + center.y + 'px';
  669. newCss[CSS_TRANSFORM] = transform.toString();
  670. css(self.elements.preview, newCss);
  671. }
  672. function _initDraggable() {
  673. var self = this,
  674. isDragging = false,
  675. originalX,
  676. originalY,
  677. originalDistance,
  678. vpRect,
  679. transform;
  680. function assignTransformCoordinates(deltaX, deltaY) {
  681. var imgRect = self.elements.preview.getBoundingClientRect(),
  682. top = transform.y + deltaY,
  683. left = transform.x + deltaX;
  684. if (self.options.enforceBoundary) {
  685. if (vpRect.top > imgRect.top + deltaY && vpRect.bottom < imgRect.bottom + deltaY) {
  686. transform.y = top;
  687. }
  688. if (vpRect.left > imgRect.left + deltaX && vpRect.right < imgRect.right + deltaX) {
  689. transform.x = left;
  690. }
  691. }
  692. else {
  693. transform.y = top;
  694. transform.x = left;
  695. }
  696. }
  697. function toggleGrabState(isDragging) {
  698. self.elements.preview.setAttribute('aria-grabbed', isDragging);
  699. self.elements.boundary.setAttribute('aria-dropeffect', isDragging? 'move': 'none');
  700. }
  701. function keyDown(ev) {
  702. var LEFT_ARROW = 37,
  703. UP_ARROW = 38,
  704. RIGHT_ARROW = 39,
  705. DOWN_ARROW = 40;
  706. if (ev.shiftKey && (ev.keyCode === UP_ARROW || ev.keyCode === DOWN_ARROW)) {
  707. var zoom;
  708. if (ev.keyCode === UP_ARROW) {
  709. zoom = parseFloat(self.elements.zoomer.value) + parseFloat(self.elements.zoomer.step)
  710. }
  711. else {
  712. zoom = parseFloat(self.elements.zoomer.value) - parseFloat(self.elements.zoomer.step)
  713. }
  714. self.setZoom(zoom);
  715. }
  716. else if (self.options.enableKeyMovement && (ev.keyCode >= 37 && ev.keyCode <= 40)) {
  717. ev.preventDefault();
  718. var movement = parseKeyDown(ev.keyCode);
  719. transform = Transform.parse(self.elements.preview);
  720. document.body.style[CSS_USERSELECT] = 'none';
  721. vpRect = self.elements.viewport.getBoundingClientRect();
  722. keyMove(movement);
  723. }
  724. function parseKeyDown(key) {
  725. switch (key) {
  726. case LEFT_ARROW:
  727. return [1, 0];
  728. case UP_ARROW:
  729. return [0, 1];
  730. case RIGHT_ARROW:
  731. return [-1, 0];
  732. case DOWN_ARROW:
  733. return [0, -1];
  734. }
  735. }
  736. }
  737. function keyMove(movement) {
  738. var deltaX = movement[0],
  739. deltaY = movement[1],
  740. newCss = {};
  741. assignTransformCoordinates(deltaX, deltaY);
  742. newCss[CSS_TRANSFORM] = transform.toString();
  743. css(self.elements.preview, newCss);
  744. _updateOverlay.call(self);
  745. document.body.style[CSS_USERSELECT] = '';
  746. _updateCenterPoint.call(self);
  747. _triggerUpdate.call(self);
  748. originalDistance = 0;
  749. }
  750. function mouseDown(ev) {
  751. if (ev.button !== undefined && ev.button !== 0) return;
  752. ev.preventDefault();
  753. if (isDragging) return;
  754. isDragging = true;
  755. originalX = ev.pageX;
  756. originalY = ev.pageY;
  757. if (ev.touches) {
  758. var touches = ev.touches[0];
  759. originalX = touches.pageX;
  760. originalY = touches.pageY;
  761. }
  762. toggleGrabState(isDragging);
  763. transform = Transform.parse(self.elements.preview);
  764. window.addEventListener('mousemove', mouseMove);
  765. window.addEventListener('touchmove', mouseMove);
  766. window.addEventListener('mouseup', mouseUp);
  767. window.addEventListener('touchend', mouseUp);
  768. document.body.style[CSS_USERSELECT] = 'none';
  769. vpRect = self.elements.viewport.getBoundingClientRect();
  770. }
  771. function mouseMove(ev) {
  772. ev.preventDefault();
  773. var pageX = ev.pageX,
  774. pageY = ev.pageY;
  775. if (ev.touches) {
  776. var touches = ev.touches[0];
  777. pageX = touches.pageX;
  778. pageY = touches.pageY;
  779. }
  780. var deltaX = pageX - originalX,
  781. deltaY = pageY - originalY,
  782. newCss = {};
  783. if (ev.type === 'touchmove') {
  784. if (ev.touches.length > 1) {
  785. var touch1 = ev.touches[0];
  786. var touch2 = ev.touches[1];
  787. var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY));
  788. if (!originalDistance) {
  789. originalDistance = dist / self._currentZoom;
  790. }
  791. var scale = dist / originalDistance;
  792. _setZoomerVal.call(self, scale);
  793. dispatchChange(self.elements.zoomer);
  794. return;
  795. }
  796. }
  797. assignTransformCoordinates(deltaX, deltaY);
  798. newCss[CSS_TRANSFORM] = transform.toString();
  799. css(self.elements.preview, newCss);
  800. _updateOverlay.call(self);
  801. originalY = pageY;
  802. originalX = pageX;
  803. }
  804. function mouseUp() {
  805. isDragging = false;
  806. toggleGrabState(isDragging);
  807. window.removeEventListener('mousemove', mouseMove);
  808. window.removeEventListener('touchmove', mouseMove);
  809. window.removeEventListener('mouseup', mouseUp);
  810. window.removeEventListener('touchend', mouseUp);
  811. document.body.style[CSS_USERSELECT] = '';
  812. _updateCenterPoint.call(self);
  813. _triggerUpdate.call(self);
  814. originalDistance = 0;
  815. }
  816. self.elements.overlay.addEventListener('mousedown', mouseDown);
  817. self.elements.viewport.addEventListener('keydown', keyDown);
  818. self.elements.overlay.addEventListener('touchstart', mouseDown);
  819. }
  820. function _updateOverlay() {
  821. if (!this.elements) return; // since this is debounced, it can be fired after destroy
  822. var self = this,
  823. boundRect = self.elements.boundary.getBoundingClientRect(),
  824. imgData = self.elements.preview.getBoundingClientRect();
  825. css(self.elements.overlay, {
  826. width: imgData.width + 'px',
  827. height: imgData.height + 'px',
  828. top: (imgData.top - boundRect.top) + 'px',
  829. left: (imgData.left - boundRect.left) + 'px'
  830. });
  831. }
  832. var _debouncedOverlay = debounce(_updateOverlay, 500);
  833. function _triggerUpdate() {
  834. var self = this,
  835. data = self.get();
  836. if (!_isVisible.call(self)) {
  837. return;
  838. }
  839. self.options.update.call(self, data);
  840. if (self.$ && typeof Prototype === 'undefined') {
  841. self.$(self.element).trigger('update.croppie', data);
  842. }
  843. else {
  844. var ev;
  845. if (window.CustomEvent) {
  846. ev = new CustomEvent('update', { detail: data });
  847. } else {
  848. ev = document.createEvent('CustomEvent');
  849. ev.initCustomEvent('update', true, true, data);
  850. }
  851. self.element.dispatchEvent(ev);
  852. }
  853. }
  854. function _isVisible() {
  855. return this.elements.preview.offsetHeight > 0 && this.elements.preview.offsetWidth > 0;
  856. }
  857. function _updatePropertiesFromImage() {
  858. var self = this,
  859. initialZoom = 1,
  860. cssReset = {},
  861. img = self.elements.preview,
  862. imgData,
  863. transformReset = new Transform(0, 0, initialZoom),
  864. originReset = new TransformOrigin(),
  865. isVisible = _isVisible.call(self);
  866. if (!isVisible || self.data.bound) {// if the croppie isn't visible or it doesn't need binding
  867. return;
  868. }
  869. self.data.bound = true;
  870. cssReset[CSS_TRANSFORM] = transformReset.toString();
  871. cssReset[CSS_TRANS_ORG] = originReset.toString();
  872. cssReset['opacity'] = 1;
  873. css(img, cssReset);
  874. imgData = self.elements.preview.getBoundingClientRect();
  875. self._originalImageWidth = imgData.width;
  876. self._originalImageHeight = imgData.height;
  877. self.data.orientation = getExifOrientation(self.elements.img);
  878. if (self.options.enableZoom) {
  879. _updateZoomLimits.call(self, true);
  880. }
  881. else {
  882. self._currentZoom = initialZoom;
  883. }
  884. transformReset.scale = self._currentZoom;
  885. cssReset[CSS_TRANSFORM] = transformReset.toString();
  886. css(img, cssReset);
  887. if (self.data.points.length) {
  888. _bindPoints.call(self, self.data.points);
  889. }
  890. else {
  891. _centerImage.call(self);
  892. }
  893. _updateCenterPoint.call(self);
  894. _updateOverlay.call(self);
  895. }
  896. function _updateZoomLimits (initial) {
  897. var self = this,
  898. minZoom = Math.max(self.options.minZoom, 0) || 0,
  899. maxZoom = self.options.maxZoom || 1.5,
  900. initialZoom,
  901. defaultInitialZoom,
  902. zoomer = self.elements.zoomer,
  903. scale = parseFloat(zoomer.value),
  904. boundaryData = self.elements.boundary.getBoundingClientRect(),
  905. imgData = naturalImageDimensions(self.elements.img, self.data.orientation),
  906. vpData = self.elements.viewport.getBoundingClientRect(),
  907. minW,
  908. minH;
  909. if (self.options.enforceBoundary) {
  910. minW = vpData.width / imgData.width;
  911. minH = vpData.height / imgData.height;
  912. minZoom = Math.max(minW, minH);
  913. }
  914. if (minZoom >= maxZoom) {
  915. maxZoom = minZoom + 1;
  916. }
  917. zoomer.min = fix(minZoom, 4);
  918. zoomer.max = fix(maxZoom, 4);
  919. if (!initial && (scale < zoomer.min || scale > zoomer.max)) {
  920. _setZoomerVal.call(self, scale < zoomer.min ? zoomer.min : zoomer.max);
  921. }
  922. else if (initial) {
  923. defaultInitialZoom = Math.max((boundaryData.width / imgData.width), (boundaryData.height / imgData.height));
  924. initialZoom = self.data.boundZoom !== null ? self.data.boundZoom : defaultInitialZoom;
  925. _setZoomerVal.call(self, initialZoom);
  926. }
  927. dispatchChange(zoomer);
  928. }
  929. function _bindPoints(points) {
  930. if (points.length !== 4) {
  931. throw "Croppie - Invalid number of points supplied: " + points;
  932. }
  933. var self = this,
  934. pointsWidth = points[2] - points[0],
  935. // pointsHeight = points[3] - points[1],
  936. vpData = self.elements.viewport.getBoundingClientRect(),
  937. boundRect = self.elements.boundary.getBoundingClientRect(),
  938. vpOffset = {
  939. left: vpData.left - boundRect.left,
  940. top: vpData.top - boundRect.top
  941. },
  942. scale = vpData.width / pointsWidth,
  943. originTop = points[1],
  944. originLeft = points[0],
  945. transformTop = (-1 * points[1]) + vpOffset.top,
  946. transformLeft = (-1 * points[0]) + vpOffset.left,
  947. newCss = {};
  948. newCss[CSS_TRANS_ORG] = originLeft + 'px ' + originTop + 'px';
  949. newCss[CSS_TRANSFORM] = new Transform(transformLeft, transformTop, scale).toString();
  950. css(self.elements.preview, newCss);
  951. _setZoomerVal.call(self, scale);
  952. self._currentZoom = scale;
  953. }
  954. function _centerImage() {
  955. var self = this,
  956. imgDim = self.elements.preview.getBoundingClientRect(),
  957. vpDim = self.elements.viewport.getBoundingClientRect(),
  958. boundDim = self.elements.boundary.getBoundingClientRect(),
  959. vpLeft = vpDim.left - boundDim.left,
  960. vpTop = vpDim.top - boundDim.top,
  961. w = vpLeft - ((imgDim.width - vpDim.width) / 2),
  962. h = vpTop - ((imgDim.height - vpDim.height) / 2),
  963. transform = new Transform(w, h, self._currentZoom);
  964. css(self.elements.preview, CSS_TRANSFORM, transform.toString());
  965. }
  966. function _transferImageToCanvas(customOrientation) {
  967. var self = this,
  968. canvas = self.elements.canvas,
  969. img = self.elements.img,
  970. ctx = canvas.getContext('2d');
  971. ctx.clearRect(0, 0, canvas.width, canvas.height);
  972. canvas.width = img.width;
  973. canvas.height = img.height;
  974. var orientation = self.options.enableOrientation && customOrientation || getExifOrientation(img);
  975. drawCanvas(canvas, img, orientation);
  976. }
  977. function _getCanvas(data) {
  978. var self = this,
  979. points = data.points,
  980. left = num(points[0]),
  981. top = num(points[1]),
  982. right = num(points[2]),
  983. bottom = num(points[3]),
  984. width = right-left,
  985. height = bottom-top,
  986. circle = data.circle,
  987. canvas = document.createElement('canvas'),
  988. ctx = canvas.getContext('2d'),
  989. startX = 0,
  990. startY = 0,
  991. canvasWidth = data.outputWidth || width,
  992. canvasHeight = data.outputHeight || height;
  993. canvas.width = canvasWidth;
  994. canvas.height = canvasHeight;
  995. if (data.backgroundColor) {
  996. ctx.fillStyle = data.backgroundColor;
  997. ctx.fillRect(0, 0, canvasWidth, canvasHeight);
  998. }
  999. // By default assume we're going to draw the entire
  1000. // source image onto the destination canvas.
  1001. var sx = left,
  1002. sy = top,
  1003. sWidth = width,
  1004. sHeight = height,
  1005. dx = 0,
  1006. dy = 0,
  1007. dWidth = canvasWidth,
  1008. dHeight = canvasHeight;
  1009. //
  1010. // Do not go outside of the original image's bounds along the x-axis.
  1011. // Handle translations when projecting onto the destination canvas.
  1012. //
  1013. // The smallest possible source x-position is 0.
  1014. if (left < 0) {
  1015. sx = 0;
  1016. dx = (Math.abs(left) / width) * canvasWidth;
  1017. }
  1018. // The largest possible source width is the original image's width.
  1019. if (sWidth + sx > self._originalImageWidth) {
  1020. sWidth = self._originalImageWidth - sx;
  1021. dWidth = (sWidth / width) * canvasWidth;
  1022. }
  1023. //
  1024. // Do not go outside of the original image's bounds along the y-axis.
  1025. //
  1026. // The smallest possible source y-position is 0.
  1027. if (top < 0) {
  1028. sy = 0;
  1029. dy = (Math.abs(top) / height) * canvasHeight;
  1030. }
  1031. // The largest possible source height is the original image's height.
  1032. if (sHeight + sy > self._originalImageHeight) {
  1033. sHeight = self._originalImageHeight - sy;
  1034. dHeight = (sHeight / height) * canvasHeight;
  1035. }
  1036. // console.table({ left, right, top, bottom, canvasWidth, canvasHeight, width, height, startX, startY, circle, sx, sy, dx, dy, sWidth, sHeight, dWidth, dHeight });
  1037. ctx.drawImage(this.elements.preview, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
  1038. if (circle) {
  1039. ctx.fillStyle = '#fff';
  1040. ctx.globalCompositeOperation = 'destination-in';
  1041. ctx.beginPath();
  1042. ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2, true);
  1043. ctx.closePath();
  1044. ctx.fill();
  1045. }
  1046. return canvas;
  1047. }
  1048. function _getHtmlResult(data) {
  1049. var points = data.points,
  1050. div = document.createElement('div'),
  1051. img = document.createElement('img'),
  1052. width = points[2] - points[0],
  1053. height = points[3] - points[1];
  1054. addClass(div, 'croppie-result');
  1055. div.appendChild(img);
  1056. css(img, {
  1057. left: (-1 * points[0]) + 'px',
  1058. top: (-1 * points[1]) + 'px'
  1059. });
  1060. img.src = data.url;
  1061. css(div, {
  1062. width: width + 'px',
  1063. height: height + 'px'
  1064. });
  1065. return div;
  1066. }
  1067. function _getBase64Result(data) {
  1068. return _getCanvas.call(this, data).toDataURL(data.format, data.quality);
  1069. }
  1070. function _getBlobResult(data) {
  1071. var self = this;
  1072. return new Promise(function (resolve) {
  1073. _getCanvas.call(self, data).toBlob(function (blob) {
  1074. resolve(blob);
  1075. }, data.format, data.quality);
  1076. });
  1077. }
  1078. function _replaceImage(img) {
  1079. if (this.elements.img.parentNode) {
  1080. Array.prototype.forEach.call(this.elements.img.classList, function(c) { img.classList.add(c); });
  1081. this.elements.img.parentNode.replaceChild(img, this.elements.img);
  1082. this.elements.preview = img; // if the img is attached to the DOM, they're not using the canvas
  1083. }
  1084. this.elements.img = img;
  1085. }
  1086. function _bind(options, cb) {
  1087. var self = this,
  1088. url,
  1089. points = [],
  1090. zoom = null,
  1091. hasExif = _hasExif.call(self);
  1092. if (typeof (options) === 'string') {
  1093. url = options;
  1094. options = {};
  1095. }
  1096. else if (Array.isArray(options)) {
  1097. points = options.slice();
  1098. }
  1099. else if (typeof (options) === 'undefined' && self.data.url) { //refreshing
  1100. _updatePropertiesFromImage.call(self);
  1101. _triggerUpdate.call(self);
  1102. return null;
  1103. }
  1104. else {
  1105. url = options.url;
  1106. points = options.points || [];
  1107. zoom = typeof(options.zoom) === 'undefined' ? null : options.zoom;
  1108. }
  1109. self.data.bound = false;
  1110. self.data.url = url || self.data.url;
  1111. self.data.boundZoom = zoom;
  1112. return loadImage(url, hasExif).then(function (img) {
  1113. _replaceImage.call(self, img);
  1114. if (!points.length) {
  1115. var natDim = naturalImageDimensions(img);
  1116. var rect = self.elements.viewport.getBoundingClientRect();
  1117. var aspectRatio = rect.width / rect.height;
  1118. var imgAspectRatio = natDim.width / natDim.height;
  1119. var width, height;
  1120. if (imgAspectRatio > aspectRatio) {
  1121. height = natDim.height;
  1122. width = height * aspectRatio;
  1123. }
  1124. else {
  1125. width = natDim.width;
  1126. height = natDim.height / aspectRatio;
  1127. }
  1128. var x0 = (natDim.width - width) / 2;
  1129. var y0 = (natDim.height - height) / 2;
  1130. var x1 = x0 + width;
  1131. var y1 = y0 + height;
  1132. self.data.points = [x0, y0, x1, y1];
  1133. }
  1134. else if (self.options.relative) {
  1135. points = [
  1136. points[0] * img.naturalWidth / 100,
  1137. points[1] * img.naturalHeight / 100,
  1138. points[2] * img.naturalWidth / 100,
  1139. points[3] * img.naturalHeight / 100
  1140. ];
  1141. }
  1142. self.data.points = points.map(function (p) {
  1143. return parseFloat(p);
  1144. });
  1145. if (self.options.useCanvas) {
  1146. _transferImageToCanvas.call(self, options.orientation);
  1147. }
  1148. _updatePropertiesFromImage.call(self);
  1149. _triggerUpdate.call(self);
  1150. cb && cb();
  1151. });
  1152. }
  1153. function fix(v, decimalPoints) {
  1154. return parseFloat(v).toFixed(decimalPoints || 0);
  1155. }
  1156. function _get() {
  1157. var self = this,
  1158. imgData = self.elements.preview.getBoundingClientRect(),
  1159. vpData = self.elements.viewport.getBoundingClientRect(),
  1160. x1 = vpData.left - imgData.left,
  1161. y1 = vpData.top - imgData.top,
  1162. widthDiff = (vpData.width - self.elements.viewport.offsetWidth) / 2, //border
  1163. heightDiff = (vpData.height - self.elements.viewport.offsetHeight) / 2,
  1164. x2 = x1 + self.elements.viewport.offsetWidth + widthDiff,
  1165. y2 = y1 + self.elements.viewport.offsetHeight + heightDiff,
  1166. scale = self._currentZoom;
  1167. if (scale === Infinity || isNaN(scale)) {
  1168. scale = 1;
  1169. }
  1170. var max = self.options.enforceBoundary ? 0 : Number.NEGATIVE_INFINITY;
  1171. x1 = Math.max(max, x1 / scale);
  1172. y1 = Math.max(max, y1 / scale);
  1173. x2 = Math.max(max, x2 / scale);
  1174. y2 = Math.max(max, y2 / scale);
  1175. return {
  1176. points: [fix(x1), fix(y1), fix(x2), fix(y2)],
  1177. zoom: scale,
  1178. orientation: self.data.orientation
  1179. };
  1180. }
  1181. var RESULT_DEFAULTS = {
  1182. type: 'canvas',
  1183. format: 'png',
  1184. quality: 1
  1185. },
  1186. RESULT_FORMATS = ['jpeg', 'webp', 'png'];
  1187. function _result(options) {
  1188. var self = this,
  1189. data = _get.call(self),
  1190. opts = deepExtend(clone(RESULT_DEFAULTS), clone(options)),
  1191. resultType = (typeof (options) === 'string' ? options : (opts.type || 'base64')),
  1192. size = opts.size || 'viewport',
  1193. format = opts.format,
  1194. quality = opts.quality,
  1195. backgroundColor = opts.backgroundColor,
  1196. circle = typeof opts.circle === 'boolean' ? opts.circle : (self.options.viewport.type === 'circle'),
  1197. vpRect = self.elements.viewport.getBoundingClientRect(),
  1198. ratio = vpRect.width / vpRect.height,
  1199. prom;
  1200. if (size === 'viewport') {
  1201. data.outputWidth = vpRect.width;
  1202. data.outputHeight = vpRect.height;
  1203. } else if (typeof size === 'object') {
  1204. if (size.width && size.height) {
  1205. data.outputWidth = size.width;
  1206. data.outputHeight = size.height;
  1207. } else if (size.width) {
  1208. data.outputWidth = size.width;
  1209. data.outputHeight = size.width / ratio;
  1210. } else if (size.height) {
  1211. data.outputWidth = size.height * ratio;
  1212. data.outputHeight = size.height;
  1213. }
  1214. }
  1215. if (RESULT_FORMATS.indexOf(format) > -1) {
  1216. data.format = 'image/' + format;
  1217. data.quality = quality;
  1218. }
  1219. data.circle = circle;
  1220. data.url = self.data.url;
  1221. data.backgroundColor = backgroundColor;
  1222. prom = new Promise(function (resolve) {
  1223. switch(resultType.toLowerCase())
  1224. {
  1225. case 'rawcanvas':
  1226. resolve(_getCanvas.call(self, data));
  1227. break;
  1228. case 'canvas':
  1229. case 'base64':
  1230. resolve(_getBase64Result.call(self, data));
  1231. break;
  1232. case 'blob':
  1233. _getBlobResult.call(self, data).then(resolve);
  1234. break;
  1235. default:
  1236. resolve(_getHtmlResult.call(self, data));
  1237. break;
  1238. }
  1239. });
  1240. return prom;
  1241. }
  1242. function _refresh() {
  1243. _updatePropertiesFromImage.call(this);
  1244. }
  1245. function _rotate(deg) {
  1246. if (!this.options.useCanvas || !this.options.enableOrientation) {
  1247. throw 'Croppie: Cannot rotate without enableOrientation && EXIF.js included';
  1248. }
  1249. var self = this,
  1250. canvas = self.elements.canvas;
  1251. self.data.orientation = getExifOffset(self.data.orientation, deg);
  1252. drawCanvas(canvas, self.elements.img, self.data.orientation);
  1253. _updateCenterPoint.call(self, true);
  1254. _updateZoomLimits.call(self);
  1255. }
  1256. function _destroy() {
  1257. var self = this;
  1258. self.element.removeChild(self.elements.boundary);
  1259. removeClass(self.element, 'croppie-container');
  1260. if (self.options.enableZoom) {
  1261. self.element.removeChild(self.elements.zoomerWrap);
  1262. }
  1263. delete self.elements;
  1264. }
  1265. if (window.jQuery) {
  1266. var $ = window.jQuery;
  1267. $.fn.croppie = function (opts) {
  1268. var ot = typeof opts;
  1269. if (ot === 'string') {
  1270. var args = Array.prototype.slice.call(arguments, 1);
  1271. var singleInst = $(this).data('croppie');
  1272. if (opts === 'get') {
  1273. return singleInst.get();
  1274. }
  1275. else if (opts === 'result') {
  1276. return singleInst.result.apply(singleInst, args);
  1277. }
  1278. else if (opts === 'bind') {
  1279. return singleInst.bind.apply(singleInst, args);
  1280. }
  1281. return this.each(function () {
  1282. var i = $(this).data('croppie');
  1283. if (!i) return;
  1284. var method = i[opts];
  1285. if ($.isFunction(method)) {
  1286. method.apply(i, args);
  1287. if (opts === 'destroy') {
  1288. $(this).removeData('croppie');
  1289. }
  1290. }
  1291. else {
  1292. throw 'Croppie ' + opts + ' method not found';
  1293. }
  1294. });
  1295. }
  1296. else {
  1297. return this.each(function () {
  1298. var i = new Croppie(this, opts);
  1299. i.$ = $;
  1300. $(this).data('croppie', i);
  1301. });
  1302. }
  1303. };
  1304. }
  1305. function Croppie(element, opts) {
  1306. if (element.className.indexOf('croppie-container') > -1) {
  1307. throw new Error("Croppie: Can't initialize croppie more than once");
  1308. }
  1309. this.element = element;
  1310. this.options = deepExtend(clone(Croppie.defaults), opts);
  1311. if (this.element.tagName.toLowerCase() === 'img') {
  1312. var origImage = this.element;
  1313. addClass(origImage, 'cr-original-image');
  1314. setAttributes(origImage, {'aria-hidden' : 'true', 'alt' : '' });
  1315. var replacementDiv = document.createElement('div');
  1316. this.element.parentNode.appendChild(replacementDiv);
  1317. replacementDiv.appendChild(origImage);
  1318. this.element = replacementDiv;
  1319. this.options.url = this.options.url || origImage.src;
  1320. }
  1321. _create.call(this);
  1322. if (this.options.url) {
  1323. var bindOpts = {
  1324. url: this.options.url,
  1325. points: this.options.points
  1326. };
  1327. delete this.options['url'];
  1328. delete this.options['points'];
  1329. _bind.call(this, bindOpts);
  1330. }
  1331. }
  1332. Croppie.defaults = {
  1333. viewport: {
  1334. width: 100,
  1335. height: 100,
  1336. type: 'square'
  1337. },
  1338. boundary: { },
  1339. orientationControls: {
  1340. enabled: true,
  1341. leftClass: '',
  1342. rightClass: ''
  1343. },
  1344. resizeControls: {
  1345. width: true,
  1346. height: true
  1347. },
  1348. customClass: '',
  1349. showZoomer: true,
  1350. enableZoom: true,
  1351. enableResize: false,
  1352. mouseWheelZoom: true,
  1353. enableExif: false,
  1354. enforceBoundary: true,
  1355. enableOrientation: false,
  1356. enableKeyMovement: true,
  1357. update: function () { }
  1358. };
  1359. Croppie.globals = {
  1360. translate: 'translate3d'
  1361. };
  1362. deepExtend(Croppie.prototype, {
  1363. bind: function (options, cb) {
  1364. return _bind.call(this, options, cb);
  1365. },
  1366. get: function () {
  1367. var data = _get.call(this);
  1368. var points = data.points;
  1369. if (this.options.relative) {
  1370. points[0] /= this.elements.img.naturalWidth / 100;
  1371. points[1] /= this.elements.img.naturalHeight / 100;
  1372. points[2] /= this.elements.img.naturalWidth / 100;
  1373. points[3] /= this.elements.img.naturalHeight / 100;
  1374. }
  1375. return data;
  1376. },
  1377. result: function (type) {
  1378. return _result.call(this, type);
  1379. },
  1380. refresh: function () {
  1381. return _refresh.call(this);
  1382. },
  1383. setZoom: function (v) {
  1384. _setZoomerVal.call(this, v);
  1385. dispatchChange(this.elements.zoomer);
  1386. },
  1387. rotate: function (deg) {
  1388. _rotate.call(this, deg);
  1389. },
  1390. destroy: function () {
  1391. return _destroy.call(this);
  1392. }
  1393. });
  1394. return Croppie;
  1395. }));