aboutsummaryrefslogtreecommitdiffstats
path: root/library/cropperjs/src/js/cropper.js
diff options
context:
space:
mode:
Diffstat (limited to 'library/cropperjs/src/js/cropper.js')
-rw-r--r--library/cropperjs/src/js/cropper.js421
1 files changed, 421 insertions, 0 deletions
diff --git a/library/cropperjs/src/js/cropper.js b/library/cropperjs/src/js/cropper.js
new file mode 100644
index 000000000..97f3511df
--- /dev/null
+++ b/library/cropperjs/src/js/cropper.js
@@ -0,0 +1,421 @@
+import DEFAULTS from './defaults';
+import TEMPLATE from './template';
+import render from './render';
+import preview from './preview';
+import events from './events';
+import handlers from './handlers';
+import change from './change';
+import methods from './methods';
+import {
+ ACTION_ALL,
+ CLASS_HIDDEN,
+ CLASS_HIDE,
+ CLASS_INVISIBLE,
+ CLASS_MODAL,
+ CLASS_MOVE,
+ DATA_ACTION,
+ EVENT_CROP,
+ EVENT_ERROR,
+ EVENT_LOAD,
+ EVENT_READY,
+ NAMESPACE,
+ REGEXP_DATA_URL,
+ REGEXP_DATA_URL_JPEG,
+ REGEXP_TAG_NAME,
+ WINDOW,
+} from './constants';
+import {
+ addClass,
+ addListener,
+ addTimestamp,
+ arrayBufferToDataURL,
+ dataURLToArrayBuffer,
+ dispatchEvent,
+ extend,
+ getData,
+ getImageNaturalSizes,
+ getOrientation,
+ isCrossOriginURL,
+ isFunction,
+ isPlainObject,
+ parseOrientation,
+ proxy,
+ removeClass,
+ removeListener,
+ setData,
+} from './utilities';
+
+const AnotherCropper = WINDOW.Cropper;
+
+class Cropper {
+ /**
+ * Create a new Cropper.
+ * @param {Element} element - The target element for cropping.
+ * @param {Object} [options={}] - The configuration options.
+ */
+ constructor(element, options = {}) {
+ if (!element || !REGEXP_TAG_NAME.test(element.tagName)) {
+ throw new Error('The first argument is required and must be an <img> or <canvas> element.');
+ }
+
+ this.element = element;
+ this.options = extend({}, DEFAULTS, isPlainObject(options) && options);
+ this.complete = false;
+ this.cropped = false;
+ this.disabled = false;
+ this.isImg = false;
+ this.limited = false;
+ this.loaded = false;
+ this.ready = false;
+ this.replaced = false;
+ this.wheeling = false;
+ this.originalUrl = '';
+ this.canvasData = null;
+ this.cropBoxData = null;
+ this.previews = null;
+ this.pointers = {};
+ this.init();
+ }
+
+ init() {
+ const { element } = this;
+ const tagName = element.tagName.toLowerCase();
+ let url;
+
+ if (getData(element, NAMESPACE)) {
+ return;
+ }
+
+ setData(element, NAMESPACE, this);
+
+ if (tagName === 'img') {
+ this.isImg = true;
+
+ // e.g.: "img/picture.jpg"
+ url = element.getAttribute('src') || '';
+ this.originalUrl = url;
+
+ // Stop when it's a blank image
+ if (!url) {
+ return;
+ }
+
+ // e.g.: "http://example.com/img/picture.jpg"
+ url = element.src;
+ } else if (tagName === 'canvas' && window.HTMLCanvasElement) {
+ url = element.toDataURL();
+ }
+
+ this.load(url);
+ }
+
+ load(url) {
+ if (!url) {
+ return;
+ }
+
+ this.url = url;
+ this.imageData = {};
+
+ const { element, options } = this;
+
+ if (!options.checkOrientation || !window.ArrayBuffer) {
+ this.clone();
+ return;
+ }
+
+ // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
+ if (REGEXP_DATA_URL.test(url)) {
+ if (REGEXP_DATA_URL_JPEG.test(url)) {
+ this.read(dataURLToArrayBuffer(url));
+ } else {
+ this.clone();
+ }
+
+ return;
+ }
+
+ const xhr = new XMLHttpRequest();
+
+ xhr.onerror = () => {
+ this.clone();
+ };
+
+ xhr.onload = () => {
+ this.read(xhr.response);
+ };
+
+ // Bust cache when there is a "crossOrigin" property
+ if (options.checkCrossOrigin && isCrossOriginURL(url) && element.crossOrigin) {
+ url = addTimestamp(url);
+ }
+
+ xhr.open('get', url);
+ xhr.responseType = 'arraybuffer';
+ xhr.withCredentials = element.crossOrigin === 'use-credentials';
+ xhr.send();
+ }
+
+ read(arrayBuffer) {
+ const { options, imageData } = this;
+ const orientation = getOrientation(arrayBuffer);
+ let rotate = 0;
+ let scaleX = 1;
+ let scaleY = 1;
+
+ if (orientation > 1) {
+ this.url = arrayBufferToDataURL(arrayBuffer, 'image/jpeg');
+ ({ rotate, scaleX, scaleY } = parseOrientation(orientation));
+ }
+
+ if (options.rotatable) {
+ imageData.rotate = rotate;
+ }
+
+ if (options.scalable) {
+ imageData.scaleX = scaleX;
+ imageData.scaleY = scaleY;
+ }
+
+ this.clone();
+ }
+
+ clone() {
+ const { element, url } = this;
+ let crossOrigin;
+ let crossOriginUrl;
+
+ if (this.options.checkCrossOrigin && isCrossOriginURL(url)) {
+ ({ crossOrigin } = element);
+
+ if (crossOrigin) {
+ crossOriginUrl = url;
+ } else {
+ crossOrigin = 'anonymous';
+
+ // Bust cache when there is not a "crossOrigin" property
+ crossOriginUrl = addTimestamp(url);
+ }
+ }
+
+ this.crossOrigin = crossOrigin;
+ this.crossOriginUrl = crossOriginUrl;
+
+ const image = document.createElement('img');
+
+ if (crossOrigin) {
+ image.crossOrigin = crossOrigin;
+ }
+
+ image.src = crossOriginUrl || url;
+
+ const start = proxy(this.start, this);
+ const stop = proxy(this.stop, this);
+
+ this.image = image;
+ this.onStart = start;
+ this.onStop = stop;
+
+ if (this.isImg) {
+ if (element.complete) {
+ this.start();
+ } else {
+ addListener(element, EVENT_LOAD, start);
+ }
+ } else {
+ addListener(image, EVENT_LOAD, start);
+ addListener(image, EVENT_ERROR, stop);
+ addClass(image, CLASS_HIDE);
+ element.parentNode.insertBefore(image, element.nextSibling);
+ }
+ }
+
+ start(event) {
+ const image = this.isImg ? this.element : this.image;
+
+ if (event) {
+ removeListener(image, EVENT_LOAD, this.onStart);
+ removeListener(image, EVENT_ERROR, this.onStop);
+ }
+
+ getImageNaturalSizes(image, (naturalWidth, naturalHeight) => {
+ extend(this.imageData, {
+ naturalWidth,
+ naturalHeight,
+ aspectRatio: naturalWidth / naturalHeight,
+ });
+ this.loaded = true;
+ this.build();
+ });
+ }
+
+ stop() {
+ const { image } = this;
+
+ removeListener(image, EVENT_LOAD, this.onStart);
+ removeListener(image, EVENT_ERROR, this.onStop);
+ image.parentNode.removeChild(image);
+ this.image = null;
+ }
+
+ build() {
+ if (!this.loaded) {
+ return;
+ }
+
+ // Unbuild first when replace
+ if (this.ready) {
+ this.unbuild();
+ }
+
+ const { element, options, image } = this;
+
+ // Create cropper elements
+ const container = element.parentNode;
+ const template = document.createElement('div');
+
+ template.innerHTML = TEMPLATE;
+
+ const cropper = template.querySelector(`.${NAMESPACE}-container`);
+ const canvas = cropper.querySelector(`.${NAMESPACE}-canvas`);
+ const dragBox = cropper.querySelector(`.${NAMESPACE}-drag-box`);
+ const cropBox = cropper.querySelector(`.${NAMESPACE}-crop-box`);
+ const face = cropBox.querySelector(`.${NAMESPACE}-face`);
+
+ this.container = container;
+ this.cropper = cropper;
+ this.canvas = canvas;
+ this.dragBox = dragBox;
+ this.cropBox = cropBox;
+ this.viewBox = cropper.querySelector(`.${NAMESPACE}-view-box`);
+ this.face = face;
+
+ canvas.appendChild(image);
+
+ // Hide the original image
+ addClass(element, CLASS_HIDDEN);
+
+ // Inserts the cropper after to the current image
+ container.insertBefore(cropper, element.nextSibling);
+
+ // Show the image if is hidden
+ if (!this.isImg) {
+ removeClass(image, CLASS_HIDE);
+ }
+
+ this.initPreview();
+ this.bind();
+
+ options.aspectRatio = Math.max(0, options.aspectRatio) || NaN;
+ options.viewMode = Math.max(0, Math.min(3, Math.round(options.viewMode))) || 0;
+
+ this.cropped = options.autoCrop;
+
+ if (options.autoCrop) {
+ if (options.modal) {
+ addClass(dragBox, CLASS_MODAL);
+ }
+ } else {
+ addClass(cropBox, CLASS_HIDDEN);
+ }
+
+ if (!options.guides) {
+ addClass(cropBox.getElementsByClassName(`${NAMESPACE}-dashed`), CLASS_HIDDEN);
+ }
+
+ if (!options.center) {
+ addClass(cropBox.getElementsByClassName(`${NAMESPACE}-center`), CLASS_HIDDEN);
+ }
+
+ if (options.background) {
+ addClass(cropper, `${NAMESPACE}-bg`);
+ }
+
+ if (!options.highlight) {
+ addClass(face, CLASS_INVISIBLE);
+ }
+
+ if (options.cropBoxMovable) {
+ addClass(face, CLASS_MOVE);
+ setData(face, DATA_ACTION, ACTION_ALL);
+ }
+
+ if (!options.cropBoxResizable) {
+ addClass(cropBox.getElementsByClassName(`${NAMESPACE}-line`), CLASS_HIDDEN);
+ addClass(cropBox.getElementsByClassName(`${NAMESPACE}-point`), CLASS_HIDDEN);
+ }
+
+ this.setDragMode(options.dragMode);
+ this.render();
+ this.ready = true;
+ this.setData(options.data);
+
+ // Call the "ready" option asynchronously to keep "image.cropper" is defined
+ this.completing = setTimeout(() => {
+ if (isFunction(options.ready)) {
+ addListener(element, EVENT_READY, options.ready, {
+ once: true,
+ });
+ }
+
+ dispatchEvent(element, EVENT_READY);
+ dispatchEvent(element, EVENT_CROP, this.getData());
+
+ this.complete = true;
+ }, 0);
+ }
+
+ unbuild() {
+ if (!this.ready) {
+ return;
+ }
+
+ if (!this.complete) {
+ clearTimeout(this.completing);
+ }
+
+ this.ready = false;
+ this.complete = false;
+ this.initialImageData = null;
+
+ // Clear `initialCanvasData` is necessary when replace
+ this.initialCanvasData = null;
+ this.initialCropBoxData = null;
+ this.containerData = null;
+ this.canvasData = null;
+
+ // Clear `cropBoxData` is necessary when replace
+ this.cropBoxData = null;
+ this.unbind();
+ this.resetPreview();
+ this.previews = null;
+ this.viewBox = null;
+ this.cropBox = null;
+ this.dragBox = null;
+ this.canvas = null;
+ this.container = null;
+ this.cropper.parentNode.removeChild(this.cropper);
+ this.cropper = null;
+ }
+
+ /**
+ * Get the no conflict cropper class.
+ * @returns {Cropper} The cropper class.
+ */
+ static noConflict() {
+ window.Cropper = AnotherCropper;
+ return Cropper;
+ }
+
+ /**
+ * Change the default options.
+ * @param {Object} options - The new default options.
+ */
+ static setDefaults(options) {
+ extend(DEFAULTS, isPlainObject(options) && options);
+ }
+}
+
+extend(Cropper.prototype, render, preview, events, handlers, change, methods);
+
+export default Cropper;