diff --git a/croppie.css b/croppie.css index 81db034..6893ca3 100644 --- a/croppie.css +++ b/croppie.css @@ -42,13 +42,13 @@ } .croppie-container .cr-resizer-vertical, -.croppie-container .cr-resizer-horisontal { +.croppie-container .cr-resizer-horizontal { position: absolute; pointer-events: all; } .croppie-container .cr-resizer-vertical::after, -.croppie-container .cr-resizer-horisontal::after { +.croppie-container .cr-resizer-horizontal::after { display: block; position: absolute; box-sizing: border-box; @@ -71,22 +71,18 @@ margin-left: -5px; } -.croppie-container .cr-resizer-horisontal { +.croppie-container .cr-resizer-horizontal { right: -5px; cursor: col-resize; width: 10px; height: 100%; } -.croppie-container .cr-resizer-horisontal::after { +.croppie-container .cr-resizer-horizontal::after { top: 50%; margin-top: -5px; } -.croppie-container .cr-original-image { - display: none; -} - .croppie-container .cr-vp-circle { border-radius: 50%; } @@ -99,7 +95,6 @@ } .croppie-container .cr-slider-wrap { - width: 75%; margin: 15px auto; text-align: center; } @@ -120,7 +115,7 @@ } .cr-slider { - width: 85%; + width: 100%; } /***********************************/ diff --git a/croppie.js b/croppie.js index 87278ca..74611f1 100644 --- a/croppie.js +++ b/croppie.js @@ -1,182 +1,328 @@ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(factory); - } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { - // CommonJS - module.exports = factory(); - } else { - // Browser globals - root.Croppie = factory(); +class Transform { + constructor(x, y, scale) { + this.x = parseFloat(x); + this.y = parseFloat(y); + this.scale = parseFloat(scale); } -}(typeof self !== 'undefined' ? self : this, function () { - // Credits to : Andrew Dupont - http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/ - function deepExtend(destination, source) { - destination = destination || {}; - for (var property in source) { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - deepExtend(destination[property], source[property]); - } else { - destination[property] = source[property]; - } - } - return destination; + + toString() { + return 'translate(' + this.x + 'px, ' + this.y + 'px' + ') scale(' + this.scale + ')'; } - function clone(object) { - return deepExtend({}, object); + static parse(v) { + if (v.style) { + return Transform.parse(v.style.transform); + } else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) { + return Transform.fromMatrix(v); + } else { + return Transform.fromString(v); + } } - function debounce(func, wait, immediate) { - var timeout; - return function () { - var context = this, args = arguments; - var later = function () { - timeout = null; - if (!immediate) func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - }; + static fromMatrix(v) { + var vals = v.substring(7).split(','); + if (!vals.length || v === 'none') { + vals = [1, 0, 0, 1, 0, 0]; + } + + return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0])); } - function dispatchChange(element) { - var event = new Event('change'); - element.dispatchEvent(event); + static fromString(v) { + var values = v.split(') '), + translate = values[0].substring("translate".length + 1).split(','), + scale = values.length > 1 ? values[1].substring(6) : 1, + x = translate.length > 1 ? translate[0] : 0, + y = translate.length > 1 ? translate[1] : 0; + + return new Transform(x, y, scale); } +} - function css(el, styles, val) { - if (typeof (styles) === 'string') { - var tmp = styles; - styles = {}; - styles[tmp] = val; +class TransformOrigin { + constructor(el) { + if (!el || !el.style.transformOrigin) { + this.x = 0; + this.y = 0; + return; } + var css = el.style.transformOrigin.split(' '); + this.x = parseFloat(css[0]); + this.y = parseFloat(css[1]); + } - for (var prop in styles) { - el.style[prop] = styles[prop]; + toString() { + return this.x + 'px ' + this.y + 'px'; + } +} + +// Credits to : Andrew Dupont - http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/ +function deepExtend(destination, source) { + destination = destination || {}; + for (var property in source) { + if (source[property] && source[property].constructor && source[property].constructor === Object) { + destination[property] = destination[property] || {}; + deepExtend(destination[property], source[property]); + } else { + destination[property] = source[property]; } } + return destination; +} + +function clone(object) { + return deepExtend({}, object); +} + +function debounce(func, wait, immediate) { + var timeout; + return function () { + var context = this, args = arguments; + var later = function () { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +} + +function dispatchChange(element) { + var event = new Event('change'); + element.dispatchEvent(event); +} + +function css(el, styles, val) { + if (typeof (styles) === 'string') { + var tmp = styles; + styles = {}; + styles[tmp] = val; + } - function num(v) { - return parseInt(v, 10); + for (var prop in styles) { + el.style[prop] = styles[prop]; } +} - /* Utilities */ - function loadImage(src) { - if (!src) { throw 'Source image missing'; } - - var img = new Image(); - img.style.opacity = '0'; - return new Promise(function (resolve, reject) { - img.removeAttribute('crossOrigin'); - if (src.match(/^https?:\/\/|^\/\//)) { - img.setAttribute('crossOrigin', 'anonymous'); +function num(v) { + return parseInt(v, 10); +} + +function fix(v, decimalPoints) { + return parseFloat(v).toFixed(decimalPoints || 0); +} + +function loadImage(src) { + if (!src) { throw 'Source image missing'; } + + var img = new Image(); + + return new Promise(function (resolve, reject) { + img.onload = () => { + resolve(img); + }; + img.onerror = reject; + img.src = src; + }); +} + +function naturalImageDimensions(img) { + return { + width: img.naturalWidth, + height: img.naturalHeight, + }; +} + +export class Croppie { + static defaults = { + viewport: { + width: 100, + height: 100, + type: 'square' + }, + boundary: { }, + resizeControls: { + width: true, + height: true + }, + customClass: '', + showZoomer: true, + enableZoom: true, + enableResize: false, + mouseWheelZoom: true, + enableKeyMovement: true, + }; + + constructor(element, opts) { + if (element.className.indexOf('croppie-container') > -1) { + throw new Error("Croppie: Can't initialize croppie more than once"); + } + this.element = element; + this.options = deepExtend(clone(Croppie.defaults), opts); + + this.#create(); + } + + bind(options) { + var url = options.url; + var points = options.points || []; + + this.data.bound = false; + this.data.url = url || this.data.url; + this.data.boundZoom = typeof(options.zoom) === 'undefined' ? null : options.zoom; + + return loadImage(url).then((img) => { + this.#replaceImage(img); + + if (!points.length) { + var natDim = naturalImageDimensions(img); + var rect = this.elements.viewport.getBoundingClientRect(); + var aspectRatio = rect.width / rect.height; + var imgAspectRatio = natDim.width / natDim.height; + var width, height; + + if (imgAspectRatio > aspectRatio) { + height = natDim.height; + width = height * aspectRatio; + } else { + width = natDim.width; + height = natDim.height / aspectRatio; + } + + var x0 = (natDim.width - width) / 2; + var y0 = (natDim.height - height) / 2; + var x1 = x0 + width; + var y1 = y0 + height; + this.data.points = [x0, y0, x1, y1]; } - img.onload = function () { - img.style.opacity = '1'; - setTimeout(function () { - resolve(img); - }, 1); - }; - img.onerror = function (ev) { - img.style.opacity = 1; - setTimeout(function () { - reject(ev); - }, 1); - }; - img.src = src; + this.data.points = points.map(function (p) { + return parseFloat(p); + }); + + this.#updatePropertiesFromImage(); + this.#triggerUpdate(); }); } - function naturalImageDimensions(img) { + get() { + var imgData = this.elements.preview.getBoundingClientRect(), + vpData = this.elements.viewport.getBoundingClientRect(), + x1 = vpData.left - imgData.left, + y1 = vpData.top - imgData.top, + widthDiff = (vpData.width - this.elements.viewport.offsetWidth) / 2, //border + heightDiff = (vpData.height - this.elements.viewport.offsetHeight) / 2, + x2 = x1 + this.elements.viewport.offsetWidth + widthDiff, + y2 = y1 + this.elements.viewport.offsetHeight + heightDiff, + scale = this._currentZoom; + + if (scale === Infinity || isNaN(scale)) { + scale = 1; + } + + var max = 0; + x1 = Math.max(max, x1 / scale); + y1 = Math.max(max, y1 / scale); + x2 = Math.max(max, x2 / scale); + y2 = Math.max(max, y2 / scale); + return { - width: img.naturalWidth, - height: img.naturalHeight, + points: [fix(x1), fix(y1), fix(x2), fix(y2)], + zoom: scale, }; } - /* CSS Transform Prototype */ - var Transform = function (x, y, scale) { - this.x = parseFloat(x); - this.y = parseFloat(y); - this.scale = parseFloat(scale); - }; + result(options) { + var RESULT_DEFAULTS = { + type: 'base64', + format: 'png', + quality: 1 + }; + var RESULT_FORMATS = ['jpeg', 'webp', 'png']; - Transform.parse = function (v) { - if (v.style) { - return Transform.parse(v.style.transform); - } - else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) { - return Transform.fromMatrix(v); - } - else { - return Transform.fromString(v); + var data = this.get(), + opts = deepExtend(clone(RESULT_DEFAULTS), clone(options)), + size = opts.size || 'viewport', + format = opts.format, + quality = opts.quality, + vpRect = this.elements.viewport.getBoundingClientRect(), + ratio = vpRect.width / vpRect.height; + + if (size === 'viewport') { + data.outputWidth = vpRect.width; + data.outputHeight = vpRect.height; + } else if (typeof size === 'object') { + if (size.width && size.height) { + data.outputWidth = size.width; + data.outputHeight = size.height; + } else if (size.width) { + data.outputWidth = size.width; + data.outputHeight = size.width / ratio; + } else if (size.height) { + data.outputWidth = size.height * ratio; + data.outputHeight = size.height; + } } - }; - Transform.fromMatrix = function (v) { - var vals = v.substring(7).split(','); - if (!vals.length || v === 'none') { - vals = [1, 0, 0, 1, 0, 0]; + if (RESULT_FORMATS.indexOf(format) > -1) { + data.format = 'image/' + format; + data.quality = quality; } - return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0])); - }; + data.url = this.data.url; - Transform.fromString = function (v) { - var values = v.split(') '), - translate = values[0].substring("translate".length + 1).split(','), - scale = values.length > 1 ? values[1].substring(6) : 1, - x = translate.length > 1 ? translate[0] : 0, - y = translate.length > 1 ? translate[1] : 0; + return new Promise((resolve, reject) => { + if (opts.type === 'rawcanvas') { + resolve(this.#getCanvas(data)); + } else if (opts.type === 'base64') { + resolve(this.#getBase64Result(data)); + } else if (opts.type === 'blob') { + this.#getBlobResult(data).then(resolve); + } else { + reject('Invalid result type: ' + opts.type); + } + }); + } - return new Transform(x, y, scale); - }; + refresh() { + this.#updatePropertiesFromImage(); + this.#triggerUpdate(); + } - Transform.prototype.toString = function () { - return 'translate(' + this.x + 'px, ' + this.y + 'px' + ') scale(' + this.scale + ')'; - }; + setZoom(v) { + this.#setZoomerVal(v); + dispatchChange(this.elements.zoomer); + } - var TransformOrigin = function (el) { - if (!el || !el.style.transformOrigin) { - this.x = 0; - this.y = 0; - return; + destroy() { + this.element.removeChild(this.elements.boundary); + this.element.classList.remove('croppie-container'); + if (this.options.enableZoom) { + this.element.removeChild(this.elements.zoomerWrap); } - var css = el.style.transformOrigin.split(' '); - this.x = parseFloat(css[0]); - this.y = parseFloat(css[1]); - }; - - TransformOrigin.prototype.toString = function () { - return this.x + 'px ' + this.y + 'px'; - }; + delete this.elements; + } - /* Private Methods */ - function _create() { - var self = this, - contClass = 'croppie-container', - customViewportClass = self.options.viewport.type ? 'cr-vp-' + self.options.viewport.type : null, + #create() { + var customViewportClass = this.options.viewport.type ? 'cr-vp-' + this.options.viewport.type : null, boundary, img, viewport, overlay, bw, bh; // Properties on class - self.data = {}; - self.elements = {}; + this.data = {}; + this.elements = {}; - boundary = self.elements.boundary = document.createElement('div'); - viewport = self.elements.viewport = document.createElement('div'); - img = self.elements.img = document.createElement('img'); - overlay = self.elements.overlay = document.createElement('div'); - self.elements.preview = img; + boundary = this.elements.boundary = document.createElement('div'); + viewport = this.elements.viewport = document.createElement('div'); + img = this.elements.img = document.createElement('img'); + overlay = this.elements.overlay = document.createElement('div'); + this.elements.preview = img; boundary.classList.add('cr-boundary'); boundary.setAttribute('aria-dropeffect', 'none'); - bw = self.options.boundary.width; - bh = self.options.boundary.height; + bw = this.options.boundary.width; + bh = this.options.boundary.height; css(boundary, { width: (bw + (isNaN(bw) ? '' : 'px')), height: (bh + (isNaN(bh) ? '' : 'px')) @@ -187,39 +333,307 @@ viewport.classList.add(customViewportClass); } css(viewport, { - width: self.options.viewport.width + 'px', - height: self.options.viewport.height + 'px' + width: this.options.viewport.width + 'px', + height: this.options.viewport.height + 'px' }); viewport.setAttribute('tabindex', 0); - self.elements.preview.classList.add('cr-image'); - self.elements.preview.setAttribute('alt', 'preview'); - self.elements.preview.setAttribute('aria-grabbed', 'false'); + this.elements.preview.classList.add('cr-image'); + this.elements.preview.setAttribute('alt', 'preview'); + this.elements.preview.setAttribute('aria-grabbed', 'false'); overlay.classList.add('cr-overlay'); - self.element.appendChild(boundary); - boundary.appendChild(self.elements.preview); + this.element.appendChild(boundary); + boundary.appendChild(this.elements.preview); boundary.appendChild(viewport); boundary.appendChild(overlay); - self.element.classList.add(contClass); - if (self.options.customClass) { - self.element.classList.add(self.options.customClass); + this.element.classList.add('croppie-container'); + if (this.options.customClass) { + this.element.classList.add(this.options.customClass); } - _initDraggable.call(this); + this.#initDraggable(); - if (self.options.enableZoom) { - _initializeZoom.call(self); + if (this.options.enableZoom) { + this.#initializeZoom(); } - if (self.options.enableResize) { - _initializeResize.call(self); - } + if (this.options.enableResize) { + this.#initializeResize(); + } + } + + #getBase64Result(data) { + return this.#getCanvas(data).toDataURL(data.format, data.quality); + } + + #getBlobResult(data) { + return new Promise((resolve) => { + this.#getCanvas(data).toBlob(function (blob) { + resolve(blob); + }, data.format, data.quality); + }); + } + + #getUnscaledCanvas(data) { + var points = data.points, + sx = num(points[0]), + sy = num(points[1]), + right = num(points[2]), + bottom = num(points[3]), + sWidth = right - sx, + sHeight = bottom - sy; + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = sWidth; + canvas.height = sHeight; + ctx.drawImage(this.elements.preview, sx, sy, sWidth, sHeight, 0, 0, canvas.width, canvas.height); + + return canvas; + } + + #getCanvas(data) { + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + canvas.width = data.outputWidth || oc.width; + canvas.height = data.outputHeight || oc.height; + + var oc = this.#getUnscaledCanvas(data); + var octx = oc.getContext('2d'); + + var cur = { + width: oc.width, + height: oc.height, + } + + while (cur.width * 0.5 > canvas.width) { + // step down size by one half for smooth scaling + let curWidth = cur.width; + let curHeight = cur.height; + + cur = { + width: Math.floor(cur.width * 0.5), + height: Math.floor(cur.height * 0.5) + }; + + octx.drawImage(oc, 0, 0, curWidth, curHeight, 0, 0, cur.width, cur.height); + } + + ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height); + return canvas; + } + + #getVirtualBoundaries(viewport) { + var scale = this._currentZoom, + vpWidth = viewport.width, + vpHeight = viewport.height, + centerFromBoundaryX = this.elements.boundary.clientWidth / 2, + centerFromBoundaryY = this.elements.boundary.clientHeight / 2, + imgRect = this.elements.preview.getBoundingClientRect(), + curImgWidth = imgRect.width, + curImgHeight = imgRect.height, + halfWidth = vpWidth / 2, + halfHeight = vpHeight / 2; + + var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1; + var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale))); + + var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1; + var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale))); + + var originMinX = (1 / scale) * halfWidth; + var originMaxX = (curImgWidth * (1 / scale)) - originMinX; + + var originMinY = (1 / scale) * halfHeight; + var originMaxY = (curImgHeight * (1 / scale)) - originMinY; + + return { + translate: { + maxX: maxX, + minX: minX, + maxY: maxY, + minY: minY + }, + origin: { + maxX: originMaxX, + minX: originMinX, + maxY: originMaxY, + minY: originMinY + } + }; + } + + #initDraggable() { + var isDragging = false, + originalX, + originalY, + originalDistance, + vpRect, + transform; + + let assignTransformCoordinates = (deltaX, deltaY) => { + var imgRect = this.elements.preview.getBoundingClientRect(), + top = transform.y + deltaY, + left = transform.x + deltaX; + + if (vpRect.top > imgRect.top + deltaY && vpRect.bottom < imgRect.bottom + deltaY) { + transform.y = top; + } + + if (vpRect.left > imgRect.left + deltaX && vpRect.right < imgRect.right + deltaX) { + transform.x = left; + } + }; + + let toggleGrabState = (isDragging) => { + this.elements.preview.setAttribute('aria-grabbed', isDragging); + this.elements.boundary.setAttribute('aria-dropeffect', isDragging? 'move': 'none'); + }; + + let keyMove = (movement, transform) => { + var deltaX = movement[0], + deltaY = movement[1], + newCss = {}; + + assignTransformCoordinates(deltaX, deltaY); + + newCss.transform = transform.toString(); + css(this.elements.preview, newCss); + this.#updateOverlay(); + document.body.style.userSelect = ''; + this.#updateCenterPoint(); + + this.#triggerUpdate(); + originalDistance = 0; + } + + let mouseMove = (ev) => { + ev.preventDefault(); + var pageX = ev.pageX, + pageY = ev.pageY; + + if (ev.touches) { + var touches = ev.touches[0]; + pageX = touches.pageX; + pageY = touches.pageY; + } + + var deltaX = pageX - originalX, + deltaY = pageY - originalY, + newCss = {}; + + if (ev.type === 'touchmove') { + if (ev.touches.length > 1) { + var touch1 = ev.touches[0]; + var touch2 = ev.touches[1]; + var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY)); + + if (!originalDistance) { + originalDistance = dist / this._currentZoom; + } + + var scale = dist / originalDistance; + + this.#setZoomerVal(scale); + dispatchChange(this.elements.zoomer); + return; + } + } + + assignTransformCoordinates(deltaX, deltaY); + + newCss.transform = transform.toString(); + css(this.elements.preview, newCss); + this.#updateOverlay(); + originalY = pageY; + originalX = pageX; + }; + + let mouseUp = () => { + isDragging = false; + toggleGrabState(isDragging); + window.removeEventListener('mousemove', mouseMove); + window.removeEventListener('touchmove', mouseMove); + window.removeEventListener('mouseup', mouseUp); + window.removeEventListener('touchend', mouseUp); + document.body.style.userSelect = ''; + this.#updateCenterPoint(); + this.#triggerUpdate(); + originalDistance = 0; + } + + let keyDown = (ev) => { + var LEFT_ARROW = 37, + UP_ARROW = 38, + RIGHT_ARROW = 39, + DOWN_ARROW = 40; + + if (ev.shiftKey && (ev.keyCode === UP_ARROW || ev.keyCode === DOWN_ARROW)) { + var zoom; + if (ev.keyCode === UP_ARROW) { + zoom = parseFloat(this.elements.zoomer.value) + parseFloat(this.elements.zoomer.step) + } + else { + zoom = parseFloat(this.elements.zoomer.value) - parseFloat(this.elements.zoomer.step) + } + this.setZoom(zoom); + } else if (this.options.enableKeyMovement && (ev.keyCode >= 37 && ev.keyCode <= 40)) { + ev.preventDefault(); + var movement = parseKeyDown(ev.keyCode); + + transform = Transform.parse(this.elements.preview); + document.body.style.userSelect = 'none'; + vpRect = this.elements.viewport.getBoundingClientRect(); + + keyMove(movement, transform); + } + + function parseKeyDown(key) { + switch (key) { + case LEFT_ARROW: + return [1, 0]; + case UP_ARROW: + return [0, 1]; + case RIGHT_ARROW: + return [-1, 0]; + case DOWN_ARROW: + return [0, -1]; + } + } + }; + + let mouseDown = (ev) => { + if (ev.button !== undefined && ev.button !== 0) return; + + ev.preventDefault(); + if (isDragging) return; + isDragging = true; + originalX = ev.pageX; + originalY = ev.pageY; + + if (ev.touches) { + var touches = ev.touches[0]; + originalX = touches.pageX; + originalY = touches.pageY; + } + toggleGrabState(isDragging); + transform = Transform.parse(this.elements.preview); + window.addEventListener('mousemove', mouseMove); + window.addEventListener('touchmove', mouseMove); + window.addEventListener('mouseup', mouseUp); + window.addEventListener('touchend', mouseUp); + document.body.style.userSelect = 'none'; + vpRect = this.elements.viewport.getBoundingClientRect(); + }; + + this.elements.overlay.addEventListener('mousedown', mouseDown); + this.elements.viewport.addEventListener('keydown', keyDown); + this.elements.overlay.addEventListener('touchstart', mouseDown); } - function _initializeResize () { - var self = this; + #initializeResize () { var wrap = document.createElement('div'); var isDragging = false; var direction; @@ -245,41 +659,11 @@ if (this.options.resizeControls.width) { hr = document.createElement('div'); - hr.classList.add('cr-resizer-horisontal'); + hr.classList.add('cr-resizer-horizontal'); wrap.appendChild(hr); } - function mouseDown(ev) { - if (ev.button !== undefined && ev.button !== 0) return; - - ev.preventDefault(); - if (isDragging) { - return; - } - - var overlayRect = self.elements.overlay.getBoundingClientRect(); - - isDragging = true; - originalX = ev.pageX; - originalY = ev.pageY; - direction = ev.currentTarget.className.indexOf('vertical') !== -1 ? 'v' : 'h'; - maxWidth = overlayRect.width; - maxHeight = overlayRect.height; - - if (ev.touches) { - var touches = ev.touches[0]; - originalX = touches.pageX; - originalY = touches.pageY; - } - - window.addEventListener('mousemove', mouseMove); - window.addEventListener('touchmove', mouseMove); - window.addEventListener('mouseup', mouseUp); - window.addEventListener('touchend', mouseUp); - document.body.style.userSelect = 'none'; - } - - function mouseMove(ev) { + let mouseMove = (ev) => { var pageX = ev.pageX; var pageY = ev.pageY; @@ -293,47 +677,76 @@ var deltaX = pageX - originalX; var deltaY = pageY - originalY; - var newHeight = self.options.viewport.height + deltaY; - var newWidth = self.options.viewport.width + deltaX; + var newHeight = this.options.viewport.height + deltaY; + var newWidth = this.options.viewport.width + deltaX; if (direction === 'v' && newHeight >= minSize && newHeight <= maxHeight) { css(wrap, { height: newHeight + 'px' }); - self.options.boundary.height += deltaY; - css(self.elements.boundary, { - height: self.options.boundary.height + 'px' + this.options.boundary.height += deltaY; + css(this.elements.boundary, { + height: this.options.boundary.height + 'px' }); - self.options.viewport.height += deltaY; - css(self.elements.viewport, { - height: self.options.viewport.height + 'px' + this.options.viewport.height += deltaY; + css(this.elements.viewport, { + height: this.options.viewport.height + 'px' }); - } - else if (direction === 'h' && newWidth >= minSize && newWidth <= maxWidth) { + } else if (direction === 'h' && newWidth >= minSize && newWidth <= maxWidth) { css(wrap, { width: newWidth + 'px' }); - self.options.boundary.width += deltaX; - css(self.elements.boundary, { - width: self.options.boundary.width + 'px' + this.options.boundary.width += deltaX; + css(this.elements.boundary, { + width: this.options.boundary.width + 'px' }); - self.options.viewport.width += deltaX; - css(self.elements.viewport, { - width: self.options.viewport.width + 'px' + this.options.viewport.width += deltaX; + css(this.elements.viewport, { + width: this.options.viewport.width + 'px' }); } - _updateOverlay.call(self); - _updateZoomLimits.call(self); - _updateCenterPoint.call(self); - _triggerUpdate.call(self); + this.#updateOverlay(); + this.#updateZoomLimits(); + this.#updateCenterPoint(); + this.#triggerUpdate(); originalY = pageY; originalX = pageX; - } + }; + + let mouseDown = (ev) => { + if (ev.button !== undefined && ev.button !== 0) return; + + ev.preventDefault(); + if (isDragging) { + return; + } + + var overlayRect = this.elements.overlay.getBoundingClientRect(); + + isDragging = true; + originalX = ev.pageX; + originalY = ev.pageY; + direction = ev.currentTarget.className.indexOf('vertical') !== -1 ? 'v' : 'h'; + maxWidth = overlayRect.width; + maxHeight = overlayRect.height; + + if (ev.touches) { + var touches = ev.touches[0]; + originalX = touches.pageX; + originalY = touches.pageY; + } + + window.addEventListener('mousemove', mouseMove); + window.addEventListener('touchmove', mouseMove); + window.addEventListener('mouseup', mouseUp); + window.addEventListener('touchend', mouseUp); + document.body.style.userSelect = 'none'; + }; function mouseUp() { isDragging = false; @@ -357,46 +770,37 @@ this.elements.boundary.appendChild(wrap); } - function _setZoomerVal(v) { - if (this.options.enableZoom) { - var z = this.elements.zoomer, - val = fix(v, 4); - - z.value = Math.max(parseFloat(z.min), Math.min(parseFloat(z.max), val)).toString(); - } - } - - function _initializeZoom() { - var self = this, - wrap = self.elements.zoomerWrap = document.createElement('div'), - zoomer = self.elements.zoomer = document.createElement('input'); + #initializeZoom() { + var wrap = this.elements.zoomerWrap = document.createElement('div'), + zoomer = this.elements.zoomer = document.createElement('input'); wrap.classList.add('cr-slider-wrap'); + wrap.style.width = this.elements.boundary.style.width; zoomer.classList.add('cr-slider'); zoomer.type = 'range'; zoomer.step = '0.0001'; zoomer.value = '1'; - zoomer.style.display = self.options.showZoomer ? '' : 'none'; + zoomer.style.display = this.options.showZoomer ? '' : 'none'; zoomer.setAttribute('aria-label', 'zoom'); - self.element.appendChild(wrap); + this.element.appendChild(wrap); wrap.appendChild(zoomer); - self._currentZoom = 1; + this._currentZoom = 1; - function change() { - _onZoom.call(self, { + let change = () => { + this.#onZoom({ value: parseFloat(zoomer.value), - origin: new TransformOrigin(self.elements.preview), - viewportRect: self.elements.viewport.getBoundingClientRect(), - transform: Transform.parse(self.elements.preview) + origin: new TransformOrigin(this.elements.preview), + viewportRect: this.elements.viewport.getBoundingClientRect(), + transform: Transform.parse(this.elements.preview) }); - } + }; - function scroll(ev) { + let scroll = (ev) => { var delta, targetZoom; - if(self.options.mouseWheelZoom === 'ctrl' && ev.ctrlKey !== true){ + if (this.options.mouseWheelZoom === 'ctrl' && ev.ctrlKey !== true){ return 0; } else if (ev.wheelDelta) { delta = ev.wheelDelta / 1200; //wheelDelta min: -120 max: 120 // max x 10 x 2 @@ -408,407 +812,197 @@ delta = 0; } - targetZoom = self._currentZoom + (delta * self._currentZoom); + targetZoom = this._currentZoom + (delta * this._currentZoom); ev.preventDefault(); - _setZoomerVal.call(self, targetZoom); - change.call(self); - } - - self.elements.zoomer.addEventListener('input', change);// this is being fired twice on keypress - self.elements.zoomer.addEventListener('change', change); - - if (self.options.mouseWheelZoom) { - self.elements.boundary.addEventListener('mousewheel', scroll); - self.elements.boundary.addEventListener('DOMMouseScroll', scroll); - } - } - - function _onZoom(ui) { - var self = this, - transform = ui ? ui.transform : Transform.parse(self.elements.preview), - vpRect = ui ? ui.viewportRect : self.elements.viewport.getBoundingClientRect(), - origin = ui ? ui.origin : new TransformOrigin(self.elements.preview); - - function applyCss() { - var transCss = {}; - transCss.transform = transform.toString(); - transCss.transformOrigin = origin.toString(); - css(self.elements.preview, transCss); - } - - self._currentZoom = ui ? ui.value : self._currentZoom; - transform.scale = self._currentZoom; - self.elements.zoomer.setAttribute('aria-valuenow', self._currentZoom); - applyCss(); - - var boundaries = _getVirtualBoundaries.call(self, vpRect), - transBoundaries = boundaries.translate, - oBoundaries = boundaries.origin; - - if (transform.x >= transBoundaries.maxX) { - origin.x = oBoundaries.minX; - transform.x = transBoundaries.maxX; - } - - if (transform.x <= transBoundaries.minX) { - origin.x = oBoundaries.maxX; - transform.x = transBoundaries.minX; - } - - if (transform.y >= transBoundaries.maxY) { - origin.y = oBoundaries.minY; - transform.y = transBoundaries.maxY; - } - - if (transform.y <= transBoundaries.minY) { - origin.y = oBoundaries.maxY; - transform.y = transBoundaries.minY; - } - applyCss(); - _debouncedOverlay.call(self); - _triggerUpdate.call(self); - } - - function _getVirtualBoundaries(viewport) { - var self = this, - scale = self._currentZoom, - vpWidth = viewport.width, - vpHeight = viewport.height, - centerFromBoundaryX = self.elements.boundary.clientWidth / 2, - centerFromBoundaryY = self.elements.boundary.clientHeight / 2, - imgRect = self.elements.preview.getBoundingClientRect(), - curImgWidth = imgRect.width, - curImgHeight = imgRect.height, - halfWidth = vpWidth / 2, - halfHeight = vpHeight / 2; - - var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1; - var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale))); - - var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1; - var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale))); - - var originMinX = (1 / scale) * halfWidth; - var originMaxX = (curImgWidth * (1 / scale)) - originMinX; - - var originMinY = (1 / scale) * halfHeight; - var originMaxY = (curImgHeight * (1 / scale)) - originMinY; - - return { - translate: { - maxX: maxX, - minX: minX, - maxY: maxY, - minY: minY - }, - origin: { - maxX: originMaxX, - minX: originMinX, - maxY: originMaxY, - minY: originMinY - } + this.#setZoomerVal(targetZoom); + change(); }; - } - - function _updateCenterPoint(rotate) { - var self = this, - scale = self._currentZoom, - data = self.elements.preview.getBoundingClientRect(), - vpData = self.elements.viewport.getBoundingClientRect(), - transform = Transform.parse(self.elements.preview.style.transform), - pc = new TransformOrigin(self.elements.preview), - top = (vpData.top - data.top) + (vpData.height / 2), - left = (vpData.left - data.left) + (vpData.width / 2), - center = {}, - adj = {}; - - if (rotate) { - var cx = pc.x; - var cy = pc.y; - var tx = transform.x; - var ty = transform.y; - - center.y = cx; - center.x = cy; - transform.y = tx; - transform.x = ty; - } - else { - center.y = top / scale; - center.x = left / scale; - adj.y = (center.y - pc.y) * (1 - scale); - adj.x = (center.x - pc.x) * (1 - scale); + this.elements.zoomer.addEventListener('input', change); // this is being fired twice on keypress + this.elements.zoomer.addEventListener('change', change); - transform.x -= adj.x; - transform.y -= adj.y; + if (this.options.mouseWheelZoom) { + this.elements.boundary.addEventListener('mousewheel', scroll); + this.elements.boundary.addEventListener('DOMMouseScroll', scroll); } - - var newCss = {}; - newCss.transformOrigin = center.x + 'px ' + center.y + 'px'; - newCss.transform = transform.toString(); - css(self.elements.preview, newCss); } - function _initDraggable() { - var self = this, - isDragging = false, - originalX, - originalY, - originalDistance, - vpRect, - transform; - - function assignTransformCoordinates(deltaX, deltaY) { - var imgRect = self.elements.preview.getBoundingClientRect(), - top = transform.y + deltaY, - left = transform.x + deltaX; - - if (vpRect.top > imgRect.top + deltaY && vpRect.bottom < imgRect.bottom + deltaY) { - transform.y = top; - } - - if (vpRect.left > imgRect.left + deltaX && vpRect.right < imgRect.right + deltaX) { - transform.x = left; - } - } - - function toggleGrabState(isDragging) { - self.elements.preview.setAttribute('aria-grabbed', isDragging); - self.elements.boundary.setAttribute('aria-dropeffect', isDragging? 'move': 'none'); - } - - function keyDown(ev) { - var LEFT_ARROW = 37, - UP_ARROW = 38, - RIGHT_ARROW = 39, - DOWN_ARROW = 40; - - if (ev.shiftKey && (ev.keyCode === UP_ARROW || ev.keyCode === DOWN_ARROW)) { - var zoom; - if (ev.keyCode === UP_ARROW) { - zoom = parseFloat(self.elements.zoomer.value) + parseFloat(self.elements.zoomer.step) - } - else { - zoom = parseFloat(self.elements.zoomer.value) - parseFloat(self.elements.zoomer.step) - } - self.setZoom(zoom); - } - else if (self.options.enableKeyMovement && (ev.keyCode >= 37 && ev.keyCode <= 40)) { - ev.preventDefault(); - var movement = parseKeyDown(ev.keyCode); - - transform = Transform.parse(self.elements.preview); - document.body.style.userSelect = 'none'; - vpRect = self.elements.viewport.getBoundingClientRect(); - keyMove(movement); - } - - function parseKeyDown(key) { - switch (key) { - case LEFT_ARROW: - return [1, 0]; - case UP_ARROW: - return [0, 1]; - case RIGHT_ARROW: - return [-1, 0]; - case DOWN_ARROW: - return [0, -1]; - } - } - } - - function keyMove(movement) { - var deltaX = movement[0], - deltaY = movement[1], - newCss = {}; - - assignTransformCoordinates(deltaX, deltaY); - - newCss.transform = transform.toString(); - css(self.elements.preview, newCss); - _updateOverlay.call(self); - document.body.style.userSelect = ''; - _updateCenterPoint.call(self); - _triggerUpdate.call(self); - originalDistance = 0; - } - - function mouseDown(ev) { - if (ev.button !== undefined && ev.button !== 0) return; - - ev.preventDefault(); - if (isDragging) return; - isDragging = true; - originalX = ev.pageX; - originalY = ev.pageY; + #setZoomerVal(v) { + if (this.options.enableZoom) { + var z = this.elements.zoomer, + val = fix(v, 4); - if (ev.touches) { - var touches = ev.touches[0]; - originalX = touches.pageX; - originalY = touches.pageY; - } - toggleGrabState(isDragging); - transform = Transform.parse(self.elements.preview); - window.addEventListener('mousemove', mouseMove); - window.addEventListener('touchmove', mouseMove); - window.addEventListener('mouseup', mouseUp); - window.addEventListener('touchend', mouseUp); - document.body.style.userSelect = 'none'; - vpRect = self.elements.viewport.getBoundingClientRect(); + z.value = Math.max(parseFloat(z.min), Math.min(parseFloat(z.max), val)).toString(); } + } - function mouseMove(ev) { - ev.preventDefault(); - var pageX = ev.pageX, - pageY = ev.pageY; - - if (ev.touches) { - var touches = ev.touches[0]; - pageX = touches.pageX; - pageY = touches.pageY; - } - - var deltaX = pageX - originalX, - deltaY = pageY - originalY, - newCss = {}; + #onZoom(ui) { + var transform = ui ? ui.transform : Transform.parse(this.elements.preview), + vpRect = ui ? ui.viewportRect : this.elements.viewport.getBoundingClientRect(), + origin = ui ? ui.origin : new TransformOrigin(this.elements.preview); - if (ev.type === 'touchmove') { - if (ev.touches.length > 1) { - var touch1 = ev.touches[0]; - var touch2 = ev.touches[1]; - var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY)); + let applyCss = () => { + var transCss = {}; + transCss.transform = transform.toString(); + transCss.transformOrigin = origin.toString(); + css(this.elements.preview, transCss); + }; - if (!originalDistance) { - originalDistance = dist / self._currentZoom; - } + this._currentZoom = ui ? ui.value : this._currentZoom; + transform.scale = this._currentZoom; + this.elements.zoomer.setAttribute('aria-valuenow', this._currentZoom); + applyCss(); - var scale = dist / originalDistance; + var boundaries = this.#getVirtualBoundaries(vpRect), + transBoundaries = boundaries.translate, + oBoundaries = boundaries.origin; - _setZoomerVal.call(self, scale); - dispatchChange(self.elements.zoomer); - return; - } - } + if (transform.x >= transBoundaries.maxX) { + origin.x = oBoundaries.minX; + transform.x = transBoundaries.maxX; + } - assignTransformCoordinates(deltaX, deltaY); + if (transform.x <= transBoundaries.minX) { + origin.x = oBoundaries.maxX; + transform.x = transBoundaries.minX; + } - newCss.transform = transform.toString(); - css(self.elements.preview, newCss); - _updateOverlay.call(self); - originalY = pageY; - originalX = pageX; + if (transform.y >= transBoundaries.maxY) { + origin.y = oBoundaries.minY; + transform.y = transBoundaries.maxY; } - function mouseUp() { - isDragging = false; - toggleGrabState(isDragging); - window.removeEventListener('mousemove', mouseMove); - window.removeEventListener('touchmove', mouseMove); - window.removeEventListener('mouseup', mouseUp); - window.removeEventListener('touchend', mouseUp); - document.body.style.userSelect = ''; - _updateCenterPoint.call(self); - _triggerUpdate.call(self); - originalDistance = 0; + if (transform.y <= transBoundaries.minY) { + origin.y = oBoundaries.maxY; + transform.y = transBoundaries.minY; } - self.elements.overlay.addEventListener('mousedown', mouseDown); - self.elements.viewport.addEventListener('keydown', keyDown); - self.elements.overlay.addEventListener('touchstart', mouseDown); - } + applyCss(); - function _updateOverlay() { - if (!this.elements) return; // since this is debounced, it can be fired after destroy - var self = this, - boundRect = self.elements.boundary.getBoundingClientRect(), - imgData = self.elements.preview.getBoundingClientRect(); + (debounce(() => { + this.#updateOverlay(); + }, 500))(); - css(self.elements.overlay, { - width: imgData.width + 'px', - height: imgData.height + 'px', - top: (imgData.top - boundRect.top) + 'px', - left: (imgData.left - boundRect.left) + 'px' - }); + this.#triggerUpdate(); } - var _debouncedOverlay = debounce(_updateOverlay, 500); + #replaceImage(img) { + if (this.elements.img.parentNode) { + Array.prototype.forEach.call(this.elements.img.classList, function(c) { img.classList.add(c); }); + this.elements.img.parentNode.replaceChild(img, this.elements.img); + this.elements.preview = img; // if the img is attached to the DOM, they're not using the canvas + } + this.elements.img = img; + } - function _triggerUpdate() { - var self = this, - data = self.get(); + #triggerUpdate() { + var data = this.get(); - if (!_isVisible.call(self)) { + if (!this.#isVisible()) { return; } - self.options.update.call(self, data); var ev = new CustomEvent('update', { detail: data }); - self.element.dispatchEvent(ev); + this.element.dispatchEvent(ev); } - function _isVisible() { + #isVisible() { return this.elements.preview.offsetHeight > 0 && this.elements.preview.offsetWidth > 0; } - function _updatePropertiesFromImage() { - var self = this, - initialZoom = 1, + #updateOverlay() { + if (!this.elements) return; // since this is debounced, it can be fired after destroy + + var boundRect = this.elements.boundary.getBoundingClientRect(); + var imgData = this.elements.preview.getBoundingClientRect(); + + css(this.elements.overlay, { + width: imgData.width + 'px', + height: imgData.height + 'px', + top: (imgData.top - boundRect.top) + 'px', + left: (imgData.left - boundRect.left) + 'px' + }); + } + + #updatePropertiesFromImage() { + var initialZoom = 1, cssReset = {}, - img = self.elements.preview, + img = this.elements.preview, imgData, transformReset = new Transform(0, 0, initialZoom), - originReset = new TransformOrigin(), - isVisible = _isVisible.call(self); + originReset = new TransformOrigin(); - if (!isVisible || self.data.bound) {// if the croppie isn't visible or it doesn't need binding + if (!this.#isVisible() || this.data.bound) {// if the croppie isn't visible or it doesn't need binding return; } - self.data.bound = true; + this.data.bound = true; cssReset.transform = transformReset.toString(); cssReset.transformOrigin = originReset.toString(); cssReset['opacity'] = 1; css(img, cssReset); - imgData = self.elements.preview.getBoundingClientRect(); + imgData = this.elements.preview.getBoundingClientRect(); - self._originalImageWidth = imgData.width; - self._originalImageHeight = imgData.height; + this._originalImageWidth = imgData.width; + this._originalImageHeight = imgData.height; - if (self.options.enableZoom) { - _updateZoomLimits.call(self, true); - } - else { - self._currentZoom = initialZoom; + if (this.options.enableZoom) { + this.#updateZoomLimits(true); + } else { + this._currentZoom = initialZoom; } - transformReset.scale = self._currentZoom; + transformReset.scale = this._currentZoom; cssReset.transform = transformReset.toString(); css(img, cssReset); - if (self.data.points.length) { - _bindPoints.call(self, self.data.points); - } - else { - _centerImage.call(self); + if (this.data.points.length) { + this.#bindPoints(this.data.points); + } else { + this.#centerImage(); } - _updateCenterPoint.call(self); - _updateOverlay.call(self); + this.#updateCenterPoint(); + this.#updateOverlay(); + } + + #updateCenterPoint() { + var scale = this._currentZoom, + data = this.elements.preview.getBoundingClientRect(), + vpData = this.elements.viewport.getBoundingClientRect(), + transform = Transform.parse(this.elements.preview.style.transform), + pc = new TransformOrigin(this.elements.preview), + top = (vpData.top - data.top) + (vpData.height / 2), + left = (vpData.left - data.left) + (vpData.width / 2), + center = {}, + adj = {}; + + center.y = top / scale; + center.x = left / scale; + + adj.y = (center.y - pc.y) * (1 - scale); + adj.x = (center.x - pc.x) * (1 - scale); + + transform.x -= adj.x; + transform.y -= adj.y; + + var newCss = {}; + newCss.transformOrigin = center.x + 'px ' + center.y + 'px'; + newCss.transform = transform.toString(); + css(this.elements.preview, newCss); } - function _updateZoomLimits (initial) { - var self = this, - maxZoom = 1, + #updateZoomLimits(initial) { + var maxZoom = 1, initialZoom, defaultInitialZoom, - zoomer = self.elements.zoomer, + zoomer = this.elements.zoomer, scale = parseFloat(zoomer.value), - boundaryData = self.elements.boundary.getBoundingClientRect(), - imgData = naturalImageDimensions(self.elements.img), - vpData = self.elements.viewport.getBoundingClientRect(), + boundaryData = this.elements.boundary.getBoundingClientRect(), + imgData = naturalImageDimensions(this.elements.img), + vpData = this.elements.viewport.getBoundingClientRect(), minW = vpData.width / imgData.width, minH = vpData.height / imgData.height, minZoom = Math.max(minW, minH); @@ -821,25 +1015,24 @@ zoomer.max = fix(maxZoom, 4); if (!initial && (scale < zoomer.min || scale > zoomer.max)) { - _setZoomerVal.call(self, scale < zoomer.min ? zoomer.min : zoomer.max); - } - else if (initial) { + this.#setZoomerVal(scale < zoomer.min ? zoomer.min : zoomer.ma); + } else if (initial) { defaultInitialZoom = Math.max((boundaryData.width / imgData.width), (boundaryData.height / imgData.height)); - initialZoom = self.data.boundZoom !== null ? self.data.boundZoom : defaultInitialZoom; - _setZoomerVal.call(self, initialZoom); + initialZoom = this.data.boundZoom !== null ? this.data.boundZoom : defaultInitialZoom; + this.#setZoomerVal(initialZoom); } dispatchChange(zoomer); } - function _bindPoints(points) { + #bindPoints(points) { if (points.length !== 4) { throw "Croppie - Invalid number of points supplied: " + points; } - var self = this, - pointsWidth = points[2] - points[0], - vpData = self.elements.viewport.getBoundingClientRect(), - boundRect = self.elements.boundary.getBoundingClientRect(), + + var pointsWidth = points[2] - points[0], + vpData = this.elements.viewport.getBoundingClientRect(), + boundRect = this.elements.boundary.getBoundingClientRect(), vpOffset = { left: vpData.left - boundRect.left, top: vpData.top - boundRect.top @@ -853,362 +1046,22 @@ newCss.transformOrigin = originLeft + 'px ' + originTop + 'px'; newCss.transform = new Transform(transformLeft, transformTop, scale).toString(); - css(self.elements.preview, newCss); + css(this.elements.preview, newCss); - _setZoomerVal.call(self, scale); - self._currentZoom = scale; + this.#setZoomerVal(scale); + this._currentZoom = scale; } - function _centerImage() { - var self = this, - imgDim = self.elements.preview.getBoundingClientRect(), - vpDim = self.elements.viewport.getBoundingClientRect(), - boundDim = self.elements.boundary.getBoundingClientRect(), + #centerImage() { + var imgDim = this.elements.preview.getBoundingClientRect(), + vpDim = this.elements.viewport.getBoundingClientRect(), + boundDim = this.elements.boundary.getBoundingClientRect(), vpLeft = vpDim.left - boundDim.left, vpTop = vpDim.top - boundDim.top, w = vpLeft - ((imgDim.width - vpDim.width) / 2), h = vpTop - ((imgDim.height - vpDim.height) / 2), - transform = new Transform(w, h, self._currentZoom); - - css(self.elements.preview, 'transform', transform.toString()); - } - - function _getCanvas(data) { - var self = this, - points = data.points, - left = num(points[0]), - top = num(points[1]), - right = num(points[2]), - bottom = num(points[3]), - width = right-left, - height = bottom-top, - circle = data.circle, - canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - canvasWidth = data.outputWidth || width, - canvasHeight = data.outputHeight || height; - - canvas.width = canvasWidth; - canvas.height = canvasHeight; - - // By default assume we're going to draw the entire - // source image onto the destination canvas. - var sx = left, - sy = top, - sWidth = width, - sHeight = height, - dx = 0, - dy = 0, - dWidth = canvasWidth, - dHeight = canvasHeight; - - // Do not go outside of the original image's bounds along the x-axis. - // Handle translations when projecting onto the destination canvas. - - // The smallest possible source x-position is 0. - if (left < 0) { - sx = 0; - dx = (Math.abs(left) / width) * canvasWidth; - } - - // The largest possible source width is the original image's width. - if (sWidth + sx > self._originalImageWidth) { - sWidth = self._originalImageWidth - sx; - dWidth = (sWidth / width) * canvasWidth; - } - - // Do not go outside of the original image's bounds along the y-axis. - - // The smallest possible source y-position is 0. - if (top < 0) { - sy = 0; - dy = (Math.abs(top) / height) * canvasHeight; - } - - // The largest possible source height is the original image's height. - if (sHeight + sy > self._originalImageHeight) { - sHeight = self._originalImageHeight - sy; - dHeight = (sHeight / height) * canvasHeight; - } - - ctx.drawImage(this.elements.preview, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); - if (circle) { - ctx.fillStyle = '#fff'; - ctx.globalCompositeOperation = 'destination-in'; - ctx.beginPath(); - ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, Math.PI * 2, true); - ctx.closePath(); - ctx.fill(); - } - return canvas; - } - - function _getBase64Result(data) { - return _getCanvas.call(this, data).toDataURL(data.format, data.quality); - } - - function _getBlobResult(data) { - var self = this; - return new Promise(function (resolve) { - _getCanvas.call(self, data).toBlob(function (blob) { - resolve(blob); - }, data.format, data.quality); - }); - } - - function _replaceImage(img) { - if (this.elements.img.parentNode) { - Array.prototype.forEach.call(this.elements.img.classList, function(c) { img.classList.add(c); }); - this.elements.img.parentNode.replaceChild(img, this.elements.img); - this.elements.preview = img; // if the img is attached to the DOM, they're not using the canvas - } - this.elements.img = img; - } - - function _bind(options, cb) { - var self = this, - url, - points = [], - zoom = null; - - if (typeof (options) === 'string') { - url = options; - options = {}; - } - else if (Array.isArray(options)) { - points = options.slice(); - } - else if (typeof (options) === 'undefined' && self.data.url) { //refreshing - _updatePropertiesFromImage.call(self); - _triggerUpdate.call(self); - return null; - } - else { - url = options.url; - points = options.points || []; - zoom = typeof(options.zoom) === 'undefined' ? null : options.zoom; - } - - self.data.bound = false; - self.data.url = url || self.data.url; - self.data.boundZoom = zoom; - - return loadImage(url).then(function (img) { - _replaceImage.call(self, img); - if (!points.length) { - var natDim = naturalImageDimensions(img); - var rect = self.elements.viewport.getBoundingClientRect(); - var aspectRatio = rect.width / rect.height; - var imgAspectRatio = natDim.width / natDim.height; - var width, height; - - if (imgAspectRatio > aspectRatio) { - height = natDim.height; - width = height * aspectRatio; - } - else { - width = natDim.width; - height = natDim.height / aspectRatio; - } - - var x0 = (natDim.width - width) / 2; - var y0 = (natDim.height - height) / 2; - var x1 = x0 + width; - var y1 = y0 + height; - self.data.points = [x0, y0, x1, y1]; - } - - self.data.points = points.map(function (p) { - return parseFloat(p); - }); - _updatePropertiesFromImage.call(self); - _triggerUpdate.call(self); - cb && cb(); - }); - } - - function fix(v, decimalPoints) { - return parseFloat(v).toFixed(decimalPoints || 0); - } - - function _get() { - var self = this, - imgData = self.elements.preview.getBoundingClientRect(), - vpData = self.elements.viewport.getBoundingClientRect(), - x1 = vpData.left - imgData.left, - y1 = vpData.top - imgData.top, - widthDiff = (vpData.width - self.elements.viewport.offsetWidth) / 2, //border - heightDiff = (vpData.height - self.elements.viewport.offsetHeight) / 2, - x2 = x1 + self.elements.viewport.offsetWidth + widthDiff, - y2 = y1 + self.elements.viewport.offsetHeight + heightDiff, - scale = self._currentZoom; - - if (scale === Infinity || isNaN(scale)) { - scale = 1; - } - - var max = 0; - x1 = Math.max(max, x1 / scale); - y1 = Math.max(max, y1 / scale); - x2 = Math.max(max, x2 / scale); - y2 = Math.max(max, y2 / scale); - - return { - points: [fix(x1), fix(y1), fix(x2), fix(y2)], - zoom: scale, - }; - } - - function _result(options) { - var RESULT_DEFAULTS = { - type: 'base64', - format: 'png', - quality: 1 - }; - var RESULT_FORMATS = ['jpeg', 'webp', 'png']; - - var self = this, - data = _get.call(self), - opts = deepExtend(clone(RESULT_DEFAULTS), clone(options)), - size = opts.size || 'viewport', - format = opts.format, - quality = opts.quality, - circle = typeof opts.circle === 'boolean' ? opts.circle : (self.options.viewport.type === 'circle'), - vpRect = self.elements.viewport.getBoundingClientRect(), - ratio = vpRect.width / vpRect.height, - prom; - - if (size === 'viewport') { - data.outputWidth = vpRect.width; - data.outputHeight = vpRect.height; - } else if (typeof size === 'object') { - if (size.width && size.height) { - data.outputWidth = size.width; - data.outputHeight = size.height; - } else if (size.width) { - data.outputWidth = size.width; - data.outputHeight = size.width / ratio; - } else if (size.height) { - data.outputWidth = size.height * ratio; - data.outputHeight = size.height; - } - } - - if (RESULT_FORMATS.indexOf(format) > -1) { - data.format = 'image/' + format; - data.quality = quality; - } - - data.circle = circle; - data.url = self.data.url; - - prom = new Promise(function (resolve) { - switch(opts.type) { - case 'rawcanvas': - resolve(_getCanvas.call(self, data)); - break; - case 'base64': - resolve(_getBase64Result.call(self, data)); - break; - case 'blob': - _getBlobResult.call(self, data).then(resolve); - break; - } - }); - return prom; - } - - function _refresh() { - _updatePropertiesFromImage.call(this); - } - - function _destroy() { - var self = this; - self.element.removeChild(self.elements.boundary); - self.element.classList.remove('croppie-container'); - if (self.options.enableZoom) { - self.element.removeChild(self.elements.zoomerWrap); - } - delete self.elements; - } - - function Croppie(element, opts) { - if (element.className.indexOf('croppie-container') > -1) { - throw new Error("Croppie: Can't initialize croppie more than once"); - } - this.element = element; - this.options = deepExtend(clone(Croppie.defaults), opts); - - if (this.element.tagName.toLowerCase() === 'img') { - var origImage = this.element; - origImage.classList.add('cr-original-image'); - origImage.setAttribute('aria-hidden', 'true'); - origImage.setAttribute('alt', ''); - var replacementDiv = document.createElement('div'); - this.element.parentNode.appendChild(replacementDiv); - replacementDiv.appendChild(origImage); - this.element = replacementDiv; - this.options.url = this.options.url || origImage.src; - } + transform = new Transform(w, h, this._currentZoom); - _create.call(this); - if (this.options.url) { - var bindOpts = { - url: this.options.url, - points: this.options.points - }; - delete this.options['url']; - delete this.options['points']; - _bind.call(this, bindOpts); - } + css(this.elements.preview, 'transform', transform.toString()); } - - Croppie.defaults = { - viewport: { - width: 100, - height: 100, - type: 'square' - }, - boundary: { }, - orientationControls: { - enabled: true, - leftClass: '', - rightClass: '' - }, - resizeControls: { - width: true, - height: true - }, - customClass: '', - showZoomer: true, - enableZoom: true, - enableResize: false, - mouseWheelZoom: true, - enableKeyMovement: true, - update: function () { } - }; - - deepExtend(Croppie.prototype, { - bind: function (options, cb) { - return _bind.call(this, options, cb); - }, - get: function () { - var data = _get.call(this); - return data; - }, - result: function (type) { - return _result.call(this, type); - }, - refresh: function () { - return _refresh.call(this); - }, - setZoom: function (v) { - _setZoomerVal.call(this, v); - dispatchChange(this.elements.zoomer); - }, - destroy: function () { - return _destroy.call(this); - } - }); - return Croppie; -})); +} diff --git a/demo/demo.css b/demo/demo.css index 67b7f5b..8ff8a4a 100644 --- a/demo/demo.css +++ b/demo/demo.css @@ -1,12 +1,3 @@ -/* -Colors -#20C1C7 -#204648 -#189094 -#61CED2 -#0C4648 -*/ - html { scroll-behavior: smooth; } @@ -25,6 +16,10 @@ body { height: auto; } +img.circle { + border-radius: 50%; +} + body, button, input { @@ -88,14 +83,6 @@ section.who { padding: 5px 0; } -input[type="number"], -input[type="text"] { - border: 1px solid #aaa; - padding: 5px; - font-size: 18px; - width: 100px; -} - button, a.btn { background-color: #189094; @@ -254,16 +241,8 @@ h2 { border: 1px solid #aaa; } -/* Sweet alert modifications */ -.sweet-alert { - width: auto; - max-width: 85%; -} - /* Grid - subset */ *, *:after, *:before { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; box-sizing: border-box; } [class*='col-'] { @@ -301,7 +280,7 @@ h2 { width: 75%; } -@media handheld, only screen and (max-width: 767px) { +@media only screen and (max-width: 767px) { .grid { width: 100%; min-width: 0; @@ -326,4 +305,4 @@ h2 { .croppie-container { padding: 30px 0; } -} \ No newline at end of file +} diff --git a/demo/demo.js b/demo/demo.js index d5bee20..6d23efb 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -1,188 +1,185 @@ -var Demo = (function() { - function popupResult(result) { - var html = ''; - Swal.fire({ - title: '', - html: html, - allowOutsideClick: true - }); - } - - function demoMain () { - var mc = document.getElementById('cropper-1'); - - var cropper1 = new Croppie(mc, { - viewport: { - width: 150, - height: 150, - type: 'circle' - }, - boundary: { - width: 300, - height: 300 - }, - }); - - mc.addEventListener('update', function (ev) { - console.log('update', ev); - }); - - var mi = document.querySelector('.js-main-image'); - mi.addEventListener('click', function (ev) { - cropper1.result({ - type: 'rawcanvas', - circle: true, - format: 'png' - }).then(function (canvas) { - popupResult({ - src: canvas.toDataURL() - }); +import { Croppie } from "../croppie.js"; + +function popupResult(result) { + var html = ''; + Swal.fire({ + title: '', + html: html, + allowOutsideClick: true + }); +} + +function demoMain () { + var mc = document.getElementById('cropper-1'); + + var cropper1 = new Croppie(mc, { + viewport: { + width: 150, + height: 150, + type: 'circle' + }, + boundary: { + width: 300, + height: 300 + }, + }); + + cropper1.bind({ url: "demo/demo-1.jpg" }); + + mc.addEventListener('update', function (ev) { + console.log('main update', ev); + }); + + var mi = document.querySelector('.js-main-image'); + mi.addEventListener('click', function (ev) { + cropper1.result({ + type: 'rawcanvas', + format: 'png' + }).then(function (canvas) { + popupResult({ + src: canvas.toDataURL(), + viewport: cropper1.options.viewport.type, }); }); - } - - function demoBasic() { - var basicEl = document.getElementById('demo-basic'); - var basic = new Croppie(basicEl, { - viewport: { - width: 150, - height: 200 - }, - boundary: { - width: 300, - height: 300 - }, - }); - - basic.bind({ - url: 'demo/cat.jpg', - points: [77,469,280,739] - }); - - var basicResult = document.querySelector('.basic-result'); - basicResult.addEventListener('click', function () { - basic.result({ type: 'base64' }).then(function (resp) { - popupResult({ - src: resp - }); + }); +} + +function demoBasic() { + var basicEl = document.getElementById('demo-basic'); + var basic = new Croppie(basicEl, { + viewport: { + width: 150, + height: 200 + }, + boundary: { + width: 300, + height: 300 + }, + }); + + basic.bind({ + url: 'demo/cat.jpg', + points: [77,469,280,739] + }); + + var basicResult = document.querySelector('.basic-result'); + basicResult.addEventListener('click', function () { + basic.result({ type: 'base64' }).then(function (resp) { + popupResult({ + src: resp }); }); - } - - function demoResizer() { - var vEl = document.getElementById('resizer-demo'), - resize = new Croppie(vEl, { - viewport: { width: 150, height: 150 }, - boundary: { width: 300, height: 300 }, - showZoomer: false, - enableResize: true, - mouseWheelZoom: 'ctrl' - }); - resize.bind({ - url: 'demo/demo-2.jpg', - zoom: 0 - }); - vEl.addEventListener('update', function (ev) { - console.log('resize update', ev); - }); - document.querySelector('.resizer-result').addEventListener('click', function (ev) { - resize.result({ type: 'blob' }).then(function (blob) { - popupResult({ - src: window.URL.createObjectURL(blob) - }); + }); +} + +function demoResizer() { + var vEl = document.getElementById('resizer-demo'), + resize = new Croppie(vEl, { + viewport: { width: 150, height: 150 }, + boundary: { width: 300, height: 300 }, + showZoomer: false, + enableResize: true, + mouseWheelZoom: 'ctrl' + }); + resize.bind({ + url: 'demo/demo-2.jpg', + zoom: 0 + }); + vEl.addEventListener('update', function (ev) { + console.log('resize update', ev); + }); + document.querySelector('.resizer-result').addEventListener('click', function (ev) { + resize.result({ type: 'blob' }).then(function (blob) { + popupResult({ + src: window.URL.createObjectURL(blob) }); }); - } - - function demoUpload() { - var uploadEl = document.getElementById('upload-demo'); - var uploadCrop = new Croppie(uploadEl, { - viewport: { - width: 200, - height: 200, - type: 'circle' - }, - boundary: { - width: 300, - height: 300 - } - }); - - function readFile(input) { - if (input.files && input.files[0]) { - var reader = new FileReader(); - - reader.onload = function (e) { - document.querySelector('.upload-demo').classList.add('ready'); - - uploadCrop.bind({ - url: e.target.result - }).then(function () { - console.log('uploadCrop bind complete'); - }); - } - - reader.readAsDataURL(input.files[0]); - } + }); +} + +function demoUpload() { + var uploadEl = document.getElementById('upload-demo'); + var uploadCrop = new Croppie(uploadEl, { + viewport: { + width: 200, + height: 200, + type: 'circle' + }, + boundary: { + width: 300, + height: 300 } + }); - var uploadEl = document.getElementById('upload'); - uploadEl.addEventListener('change', function () { - readFile(uploadEl); - }); + function readFile(input) { + if (input.files && input.files[0]) { + var reader = new FileReader(); - document.querySelector('.upload-result').addEventListener('click', function (ev) { - uploadCrop.result({ - type: 'base64', - size: 'viewport' - }).then(function (resp) { - popupResult({ - src: resp - }); - }); - }); - } + reader.onload = function (e) { + document.querySelector('.upload-demo').classList.add('ready'); - function demoHidden() { - var hidEl = document.getElementById('hidden-demo'); - var hiddenCrop = new Croppie(hidEl, { - viewport: { - width: 175, - height: 175, - type: 'circle' - }, - boundary: { - width: 200, - height: 200 + uploadCrop.bind({ + url: e.target.result + }).then(function () { + console.log('uploadCrop bind complete'); + }); } - }); - hiddenCrop.bind({ - url: 'demo/demo-3.jpg' - }); - - document.querySelector('.toggle-hidden').addEventListener('click', function () { - toggle(hidEl); - hiddenCrop.bind(); // refresh - }); - } - function toggle(el) { - if (el.style.display == 'none') { - el.style.display = ''; - } else { - el.style.display = 'none'; + reader.readAsDataURL(input.files[0]); } } - function init() { - demoMain(); - demoBasic(); - demoResizer(); - demoUpload(); - demoHidden(); + var uploadEl = document.getElementById('upload'); + uploadEl.addEventListener('change', function () { + readFile(uploadEl); + }); + + document.querySelector('.upload-result').addEventListener('click', function (ev) { + uploadCrop.result({ + type: 'base64', + size: 'viewport' + }).then(function (resp) { + popupResult({ + src: resp, + viewport: uploadCrop.options.viewport.type, + }); + }); + }); +} + +function demoHidden() { + var hidEl = document.getElementById('hidden-demo'); + var hiddenCrop = new Croppie(hidEl, { + viewport: { + width: 175, + height: 175, + type: 'circle' + }, + boundary: { + width: 200, + height: 200 + } + }); + hiddenCrop.bind({ + url: 'demo/demo-3.jpg' + }); + + document.querySelector('.toggle-hidden').addEventListener('click', function () { + toggle(hidEl); + hiddenCrop.refresh(); + }); +} + +function toggle(el) { + if (el.style.display == 'none') { + el.style.display = ''; + } else { + el.style.display = 'none'; } +} - return { - init: init - }; -})(); +demoMain(); +demoBasic(); +demoResizer(); +demoUpload(); +demoHidden(); diff --git a/index.html b/index.html index c7bcd45..ba15872 100644 --- a/index.html +++ b/index.html @@ -41,7 +41,7 @@

- +
@@ -51,14 +51,8 @@

<div class="demo"></div>
 <script>
-    new Croppie(document.querySelector('.demo'), {
-        url: 'demo/demo-1.jpg',
-    });
-</script>
-<!-- or even simpler -->
-<img class="my-image" src="demo/demo-1.jpg" />
-<script>
-    new Croppie(document.querySelector('.my-image'));
+    var c = new Croppie(document.querySelector('.demo'));
+    c.bind({ url: 'demo/demo-1.jpg' });
 </script>
 
@@ -164,8 +158,12 @@

Methods

destroy()

Destroy a croppie instance and remove it from the DOM

+
  • + refresh() +

    Recalculate points for the croppie image. Necessary if croppie instance was bound to a hidden element.

    +
  • - result({ type, size, format, quality, circle })Promise + result({ type, size, format, quality })Promise

    Get the resulting crop of the image.

    Parameters
    @@ -209,9 +207,6 @@

    Methods

  • Default:1
  • -
  • - circle force the result to be cropped into a circle -
  • Valid Values:true | false
  • @@ -294,6 +289,7 @@

    Demos

    }); resize.bind({ url: 'demo/demo-2.jpg', + zoom: 0, }); //on button click resize.result({ type: 'blob' }).then(function (blob) { @@ -350,9 +346,9 @@

    Demos

    Hidden Example -

    When binding a croppie element that isn't visible, i.e., in a modal - you'll need to call bind again on your croppie instance, to indicate to croppie that the position has changed and it needs to recalculate its points.

    +

    When binding a croppie element that isn't visible, i.e., in a modal - you'll need to call refresh on your croppie instance, to indicate to croppie that the position has changed and it needs to recalculate its points.

    -
    hiddenCrop.bind();
    +
    hiddenCrop.refresh();
    @@ -427,11 +423,9 @@

    Who

    - - +