843 lines
20 KiB
JavaScript
843 lines
20 KiB
JavaScript
import {
|
|
CLASS_CROP,
|
|
CLASS_DISABLED,
|
|
CLASS_HIDDEN,
|
|
CLASS_MODAL,
|
|
CLASS_MOVE,
|
|
DATA_ACTION,
|
|
DRAG_MODE_CROP,
|
|
DRAG_MODE_MOVE,
|
|
DRAG_MODE_NONE,
|
|
EVENT_LOAD,
|
|
EVENT_ZOOM,
|
|
NAMESPACE,
|
|
} from './constants';
|
|
import {
|
|
addClass,
|
|
dispatchEvent,
|
|
each,
|
|
extend,
|
|
getAdjustedSizes,
|
|
getOffset,
|
|
getPointersCenter,
|
|
getSourceCanvas,
|
|
isFunction,
|
|
isNumber,
|
|
isPlainObject,
|
|
isUndefined,
|
|
normalizeDecimalNumber,
|
|
removeClass,
|
|
removeData,
|
|
removeListener,
|
|
setData,
|
|
toggleClass,
|
|
} from './utilities';
|
|
|
|
export default {
|
|
// Show the crop box manually
|
|
crop() {
|
|
if (this.ready && !this.disabled) {
|
|
if (!this.cropped) {
|
|
this.cropped = true;
|
|
this.limitCropBox(true, true);
|
|
|
|
if (this.options.modal) {
|
|
addClass(this.dragBox, CLASS_MODAL);
|
|
}
|
|
|
|
removeClass(this.cropBox, CLASS_HIDDEN);
|
|
}
|
|
|
|
this.setCropBoxData(this.initialCropBoxData);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// Reset the image and crop box to their initial states
|
|
reset() {
|
|
if (this.ready && !this.disabled) {
|
|
this.imageData = extend({}, this.initialImageData);
|
|
this.canvasData = extend({}, this.initialCanvasData);
|
|
this.cropBoxData = extend({}, this.initialCropBoxData);
|
|
this.renderCanvas();
|
|
|
|
if (this.cropped) {
|
|
this.renderCropBox();
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// Clear the crop box
|
|
clear() {
|
|
if (this.cropped && !this.disabled) {
|
|
extend(this.cropBoxData, {
|
|
left: 0,
|
|
top: 0,
|
|
width: 0,
|
|
height: 0,
|
|
});
|
|
|
|
this.cropped = false;
|
|
this.renderCropBox();
|
|
this.limitCanvas(true, true);
|
|
|
|
// Render canvas after crop box rendered
|
|
this.renderCanvas();
|
|
removeClass(this.dragBox, CLASS_MODAL);
|
|
addClass(this.cropBox, CLASS_HIDDEN);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Replace the image's src and rebuild the cropper
|
|
* @param {string} url - The new URL.
|
|
* @param {boolean} [onlyColorChanged] - Indicate if the new image only changed color.
|
|
* @returns {Object} this
|
|
*/
|
|
replace(url, onlyColorChanged = false) {
|
|
if (!this.disabled && url) {
|
|
if (this.isImg) {
|
|
this.element.src = url;
|
|
}
|
|
|
|
if (onlyColorChanged) {
|
|
this.url = url;
|
|
this.image.src = url;
|
|
|
|
if (this.ready) {
|
|
this.image2.src = url;
|
|
|
|
each(this.previews, (element) => {
|
|
element.getElementsByTagName('img')[0].src = url;
|
|
});
|
|
}
|
|
} else {
|
|
if (this.isImg) {
|
|
this.replaced = true;
|
|
}
|
|
|
|
// Clear previous data
|
|
this.options.data = null;
|
|
this.load(url);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// Enable (unfreeze) the cropper
|
|
enable() {
|
|
if (this.ready) {
|
|
this.disabled = false;
|
|
removeClass(this.cropper, CLASS_DISABLED);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// Disable (freeze) the cropper
|
|
disable() {
|
|
if (this.ready) {
|
|
this.disabled = true;
|
|
addClass(this.cropper, CLASS_DISABLED);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// Destroy the cropper and remove the instance from the image
|
|
destroy() {
|
|
const { element, image } = this;
|
|
|
|
if (this.loaded) {
|
|
if (this.isImg && this.replaced) {
|
|
element.src = this.originalUrl;
|
|
}
|
|
|
|
this.unbuild();
|
|
removeClass(element, CLASS_HIDDEN);
|
|
} else if (this.isImg) {
|
|
removeListener(element, EVENT_LOAD, this.onStart);
|
|
} else if (image) {
|
|
image.parentNode.removeChild(image);
|
|
}
|
|
|
|
removeData(element, NAMESPACE);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Move the canvas with relative offsets
|
|
* @param {number} offsetX - The relative offset distance on the x-axis.
|
|
* @param {number} offsetY - The relative offset distance on the y-axis.
|
|
* @returns {Object} this
|
|
*/
|
|
move(offsetX, offsetY) {
|
|
const { left, top } = this.canvasData;
|
|
|
|
return this.moveTo(
|
|
isUndefined(offsetX) ? offsetX : (left + Number(offsetX)),
|
|
isUndefined(offsetY) ? offsetY : (top + Number(offsetY)),
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Move the canvas to an absolute point
|
|
* @param {number} x - The x-axis coordinate.
|
|
* @param {number} [y=x] - The y-axis coordinate.
|
|
* @returns {Object} this
|
|
*/
|
|
moveTo(x, y = x) {
|
|
const { canvasData } = this;
|
|
let changed = false;
|
|
|
|
x = Number(x);
|
|
y = Number(y);
|
|
|
|
if (this.ready && !this.disabled && this.options.movable) {
|
|
if (isNumber(x)) {
|
|
canvasData.left = x;
|
|
changed = true;
|
|
}
|
|
|
|
if (isNumber(y)) {
|
|
canvasData.top = y;
|
|
changed = true;
|
|
}
|
|
|
|
if (changed) {
|
|
this.renderCanvas(true);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Zoom the canvas with a relative ratio
|
|
* @param {number} ratio - The target ratio.
|
|
* @param {Event} _originalEvent - The original event if any.
|
|
* @returns {Object} this
|
|
*/
|
|
zoom(ratio, _originalEvent) {
|
|
const { canvasData } = this;
|
|
|
|
ratio = Number(ratio);
|
|
|
|
if (ratio < 0) {
|
|
ratio = 1 / (1 - ratio);
|
|
} else {
|
|
ratio = 1 + ratio;
|
|
}
|
|
|
|
return this.zoomTo((canvasData.width * ratio) / canvasData.naturalWidth, null, _originalEvent);
|
|
},
|
|
|
|
/**
|
|
* Zoom the canvas to an absolute ratio
|
|
* @param {number} ratio - The target ratio.
|
|
* @param {Object} pivot - The zoom pivot point coordinate.
|
|
* @param {Event} _originalEvent - The original event if any.
|
|
* @returns {Object} this
|
|
*/
|
|
zoomTo(ratio, pivot, _originalEvent) {
|
|
const { options, canvasData } = this;
|
|
const {
|
|
width,
|
|
height,
|
|
naturalWidth,
|
|
naturalHeight,
|
|
} = canvasData;
|
|
|
|
ratio = Number(ratio);
|
|
|
|
if (ratio >= 0 && this.ready && !this.disabled && options.zoomable) {
|
|
const newWidth = naturalWidth * ratio;
|
|
const newHeight = naturalHeight * ratio;
|
|
|
|
if (dispatchEvent(this.element, EVENT_ZOOM, {
|
|
originalEvent: _originalEvent,
|
|
oldRatio: width / naturalWidth,
|
|
ratio: newWidth / naturalWidth,
|
|
}) === false) {
|
|
return this;
|
|
}
|
|
|
|
if (_originalEvent) {
|
|
const { pointers } = this;
|
|
const offset = getOffset(this.cropper);
|
|
const center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : {
|
|
pageX: _originalEvent.pageX,
|
|
pageY: _originalEvent.pageY,
|
|
};
|
|
|
|
// Zoom from the triggering point of the event
|
|
canvasData.left -= (newWidth - width) * (
|
|
((center.pageX - offset.left) - canvasData.left) / width
|
|
);
|
|
canvasData.top -= (newHeight - height) * (
|
|
((center.pageY - offset.top) - canvasData.top) / height
|
|
);
|
|
} else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {
|
|
canvasData.left -= (newWidth - width) * (
|
|
(pivot.x - canvasData.left) / width
|
|
);
|
|
canvasData.top -= (newHeight - height) * (
|
|
(pivot.y - canvasData.top) / height
|
|
);
|
|
} else {
|
|
// Zoom from the center of the canvas
|
|
canvasData.left -= (newWidth - width) / 2;
|
|
canvasData.top -= (newHeight - height) / 2;
|
|
}
|
|
|
|
canvasData.width = newWidth;
|
|
canvasData.height = newHeight;
|
|
this.renderCanvas(true);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Rotate the canvas with a relative degree
|
|
* @param {number} degree - The rotate degree.
|
|
* @returns {Object} this
|
|
*/
|
|
rotate(degree) {
|
|
return this.rotateTo((this.imageData.rotate || 0) + Number(degree));
|
|
},
|
|
|
|
/**
|
|
* Rotate the canvas to an absolute degree
|
|
* @param {number} degree - The rotate degree.
|
|
* @returns {Object} this
|
|
*/
|
|
rotateTo(degree) {
|
|
degree = Number(degree);
|
|
|
|
if (isNumber(degree) && this.ready && !this.disabled && this.options.rotatable) {
|
|
this.imageData.rotate = degree % 360;
|
|
this.renderCanvas(true, true);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Scale the image on the x-axis.
|
|
* @param {number} scaleX - The scale ratio on the x-axis.
|
|
* @returns {Object} this
|
|
*/
|
|
scaleX(scaleX) {
|
|
const { scaleY } = this.imageData;
|
|
|
|
return this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
|
|
},
|
|
|
|
/**
|
|
* Scale the image on the y-axis.
|
|
* @param {number} scaleY - The scale ratio on the y-axis.
|
|
* @returns {Object} this
|
|
*/
|
|
scaleY(scaleY) {
|
|
const { scaleX } = this.imageData;
|
|
|
|
return this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
|
|
},
|
|
|
|
/**
|
|
* Scale the image
|
|
* @param {number} scaleX - The scale ratio on the x-axis.
|
|
* @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
|
|
* @returns {Object} this
|
|
*/
|
|
scale(scaleX, scaleY = scaleX) {
|
|
const { imageData } = this;
|
|
let transformed = false;
|
|
|
|
scaleX = Number(scaleX);
|
|
scaleY = Number(scaleY);
|
|
|
|
if (this.ready && !this.disabled && this.options.scalable) {
|
|
if (isNumber(scaleX)) {
|
|
imageData.scaleX = scaleX;
|
|
transformed = true;
|
|
}
|
|
|
|
if (isNumber(scaleY)) {
|
|
imageData.scaleY = scaleY;
|
|
transformed = true;
|
|
}
|
|
|
|
if (transformed) {
|
|
this.renderCanvas(true, true);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get the cropped area position and size data (base on the original image)
|
|
* @param {boolean} [rounded=false] - Indicate if round the data values or not.
|
|
* @returns {Object} The result cropped data.
|
|
*/
|
|
getData(rounded = false) {
|
|
const {
|
|
options,
|
|
imageData,
|
|
canvasData,
|
|
cropBoxData,
|
|
} = this;
|
|
let data;
|
|
|
|
if (this.ready && this.cropped) {
|
|
data = {
|
|
x: cropBoxData.left - canvasData.left,
|
|
y: cropBoxData.top - canvasData.top,
|
|
width: cropBoxData.width,
|
|
height: cropBoxData.height,
|
|
};
|
|
|
|
const ratio = imageData.width / imageData.naturalWidth;
|
|
|
|
each(data, (n, i) => {
|
|
n /= ratio;
|
|
data[i] = rounded ? Math.round(n) : n;
|
|
});
|
|
} else {
|
|
data = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0,
|
|
};
|
|
}
|
|
|
|
if (options.rotatable) {
|
|
data.rotate = imageData.rotate || 0;
|
|
}
|
|
|
|
if (options.scalable) {
|
|
data.scaleX = imageData.scaleX || 1;
|
|
data.scaleY = imageData.scaleY || 1;
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
/**
|
|
* Set the cropped area position and size with new data
|
|
* @param {Object} data - The new data.
|
|
* @returns {Object} this
|
|
*/
|
|
setData(data) {
|
|
const { options, imageData, canvasData } = this;
|
|
const cropBoxData = {};
|
|
|
|
if (isFunction(data)) {
|
|
data = data.call(this.element);
|
|
}
|
|
|
|
if (this.ready && !this.disabled && isPlainObject(data)) {
|
|
let transformed = false;
|
|
|
|
if (options.rotatable) {
|
|
if (isNumber(data.rotate) && data.rotate !== imageData.rotate) {
|
|
imageData.rotate = data.rotate;
|
|
transformed = true;
|
|
}
|
|
}
|
|
|
|
if (options.scalable) {
|
|
if (isNumber(data.scaleX) && data.scaleX !== imageData.scaleX) {
|
|
imageData.scaleX = data.scaleX;
|
|
transformed = true;
|
|
}
|
|
|
|
if (isNumber(data.scaleY) && data.scaleY !== imageData.scaleY) {
|
|
imageData.scaleY = data.scaleY;
|
|
transformed = true;
|
|
}
|
|
}
|
|
|
|
if (transformed) {
|
|
this.renderCanvas(true, true);
|
|
}
|
|
|
|
const ratio = imageData.width / imageData.naturalWidth;
|
|
|
|
if (isNumber(data.x)) {
|
|
cropBoxData.left = (data.x * ratio) + canvasData.left;
|
|
}
|
|
|
|
if (isNumber(data.y)) {
|
|
cropBoxData.top = (data.y * ratio) + canvasData.top;
|
|
}
|
|
|
|
if (isNumber(data.width)) {
|
|
cropBoxData.width = data.width * ratio;
|
|
}
|
|
|
|
if (isNumber(data.height)) {
|
|
cropBoxData.height = data.height * ratio;
|
|
}
|
|
|
|
this.setCropBoxData(cropBoxData);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get the container size data.
|
|
* @returns {Object} The result container data.
|
|
*/
|
|
getContainerData() {
|
|
return this.ready ? extend({}, this.containerData) : {};
|
|
},
|
|
|
|
/**
|
|
* Get the image position and size data.
|
|
* @returns {Object} The result image data.
|
|
*/
|
|
getImageData() {
|
|
return this.loaded ? extend({}, this.imageData) : {};
|
|
},
|
|
|
|
/**
|
|
* Get the canvas position and size data.
|
|
* @returns {Object} The result canvas data.
|
|
*/
|
|
getCanvasData() {
|
|
const { canvasData } = this;
|
|
const data = {};
|
|
|
|
if (this.ready) {
|
|
each([
|
|
'left',
|
|
'top',
|
|
'width',
|
|
'height',
|
|
'naturalWidth',
|
|
'naturalHeight',
|
|
], (n) => {
|
|
data[n] = canvasData[n];
|
|
});
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
/**
|
|
* Set the canvas position and size with new data.
|
|
* @param {Object} data - The new canvas data.
|
|
* @returns {Object} this
|
|
*/
|
|
setCanvasData(data) {
|
|
const { canvasData } = this;
|
|
const { aspectRatio } = canvasData;
|
|
|
|
if (isFunction(data)) {
|
|
data = data.call(this.element);
|
|
}
|
|
|
|
if (this.ready && !this.disabled && isPlainObject(data)) {
|
|
if (isNumber(data.left)) {
|
|
canvasData.left = data.left;
|
|
}
|
|
|
|
if (isNumber(data.top)) {
|
|
canvasData.top = data.top;
|
|
}
|
|
|
|
if (isNumber(data.width)) {
|
|
canvasData.width = data.width;
|
|
canvasData.height = data.width / aspectRatio;
|
|
} else if (isNumber(data.height)) {
|
|
canvasData.height = data.height;
|
|
canvasData.width = data.height * aspectRatio;
|
|
}
|
|
|
|
this.renderCanvas(true);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get the crop box position and size data.
|
|
* @returns {Object} The result crop box data.
|
|
*/
|
|
getCropBoxData() {
|
|
const { cropBoxData } = this;
|
|
let data;
|
|
|
|
if (this.ready && this.cropped) {
|
|
data = {
|
|
left: cropBoxData.left,
|
|
top: cropBoxData.top,
|
|
width: cropBoxData.width,
|
|
height: cropBoxData.height,
|
|
};
|
|
}
|
|
|
|
return data || {};
|
|
},
|
|
|
|
/**
|
|
* Set the crop box position and size with new data.
|
|
* @param {Object} data - The new crop box data.
|
|
* @returns {Object} this
|
|
*/
|
|
setCropBoxData(data) {
|
|
const { cropBoxData } = this;
|
|
const { aspectRatio } = this.options;
|
|
let widthChanged;
|
|
let heightChanged;
|
|
|
|
if (isFunction(data)) {
|
|
data = data.call(this.element);
|
|
}
|
|
|
|
if (this.ready && this.cropped && !this.disabled && isPlainObject(data)) {
|
|
if (isNumber(data.left)) {
|
|
cropBoxData.left = data.left;
|
|
}
|
|
|
|
if (isNumber(data.top)) {
|
|
cropBoxData.top = data.top;
|
|
}
|
|
|
|
if (isNumber(data.width) && data.width !== cropBoxData.width) {
|
|
widthChanged = true;
|
|
cropBoxData.width = data.width;
|
|
}
|
|
|
|
if (isNumber(data.height) && data.height !== cropBoxData.height) {
|
|
heightChanged = true;
|
|
cropBoxData.height = data.height;
|
|
}
|
|
|
|
if (aspectRatio) {
|
|
if (widthChanged) {
|
|
cropBoxData.height = cropBoxData.width / aspectRatio;
|
|
} else if (heightChanged) {
|
|
cropBoxData.width = cropBoxData.height * aspectRatio;
|
|
}
|
|
}
|
|
|
|
this.renderCropBox();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get a canvas drawn the cropped image.
|
|
* @param {Object} [options={}] - The config options.
|
|
* @returns {HTMLCanvasElement} - The result canvas.
|
|
*/
|
|
getCroppedCanvas(options = {}) {
|
|
if (!this.ready || !window.HTMLCanvasElement) {
|
|
return null;
|
|
}
|
|
|
|
const { canvasData } = this;
|
|
const source = getSourceCanvas(this.image, this.imageData, canvasData, options);
|
|
|
|
// Returns the source canvas if it is not cropped.
|
|
if (!this.cropped) {
|
|
return source;
|
|
}
|
|
|
|
let {
|
|
x: initialX,
|
|
y: initialY,
|
|
width: initialWidth,
|
|
height: initialHeight,
|
|
} = this.getData();
|
|
const ratio = source.width / Math.floor(canvasData.naturalWidth);
|
|
|
|
if (ratio !== 1) {
|
|
initialX *= ratio;
|
|
initialY *= ratio;
|
|
initialWidth *= ratio;
|
|
initialHeight *= ratio;
|
|
}
|
|
|
|
const aspectRatio = initialWidth / initialHeight;
|
|
const maxSizes = getAdjustedSizes({
|
|
aspectRatio,
|
|
width: options.maxWidth || Infinity,
|
|
height: options.maxHeight || Infinity,
|
|
});
|
|
const minSizes = getAdjustedSizes({
|
|
aspectRatio,
|
|
width: options.minWidth || 0,
|
|
height: options.minHeight || 0,
|
|
}, 'cover');
|
|
let {
|
|
width,
|
|
height,
|
|
} = getAdjustedSizes({
|
|
aspectRatio,
|
|
width: options.width || (ratio !== 1 ? source.width : initialWidth),
|
|
height: options.height || (ratio !== 1 ? source.height : initialHeight),
|
|
});
|
|
|
|
width = Math.min(maxSizes.width, Math.max(minSizes.width, width));
|
|
height = Math.min(maxSizes.height, Math.max(minSizes.height, height));
|
|
|
|
const canvas = document.createElement('canvas');
|
|
const context = canvas.getContext('2d');
|
|
|
|
canvas.width = normalizeDecimalNumber(width);
|
|
canvas.height = normalizeDecimalNumber(height);
|
|
|
|
context.fillStyle = options.fillColor || 'transparent';
|
|
context.fillRect(0, 0, width, height);
|
|
|
|
const { imageSmoothingEnabled = true, imageSmoothingQuality } = options;
|
|
|
|
context.imageSmoothingEnabled = imageSmoothingEnabled;
|
|
|
|
if (imageSmoothingQuality) {
|
|
context.imageSmoothingQuality = imageSmoothingQuality;
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
|
|
const sourceWidth = source.width;
|
|
const sourceHeight = source.height;
|
|
|
|
// Source canvas parameters
|
|
let srcX = initialX;
|
|
let srcY = initialY;
|
|
let srcWidth;
|
|
let srcHeight;
|
|
|
|
// Destination canvas parameters
|
|
let dstX;
|
|
let dstY;
|
|
let dstWidth;
|
|
let dstHeight;
|
|
|
|
if (srcX <= -initialWidth || srcX > sourceWidth) {
|
|
srcX = 0;
|
|
srcWidth = 0;
|
|
dstX = 0;
|
|
dstWidth = 0;
|
|
} else if (srcX <= 0) {
|
|
dstX = -srcX;
|
|
srcX = 0;
|
|
srcWidth = Math.min(sourceWidth, initialWidth + srcX);
|
|
dstWidth = srcWidth;
|
|
} else if (srcX <= sourceWidth) {
|
|
dstX = 0;
|
|
srcWidth = Math.min(initialWidth, sourceWidth - srcX);
|
|
dstWidth = srcWidth;
|
|
}
|
|
|
|
if (srcWidth <= 0 || srcY <= -initialHeight || srcY > sourceHeight) {
|
|
srcY = 0;
|
|
srcHeight = 0;
|
|
dstY = 0;
|
|
dstHeight = 0;
|
|
} else if (srcY <= 0) {
|
|
dstY = -srcY;
|
|
srcY = 0;
|
|
srcHeight = Math.min(sourceHeight, initialHeight + srcY);
|
|
dstHeight = srcHeight;
|
|
} else if (srcY <= sourceHeight) {
|
|
dstY = 0;
|
|
srcHeight = Math.min(initialHeight, sourceHeight - srcY);
|
|
dstHeight = srcHeight;
|
|
}
|
|
|
|
// All the numerical parameters should be integer for `drawImage`
|
|
// https://github.com/fengyuanchen/cropper/issues/476
|
|
const params = [
|
|
srcX,
|
|
srcY,
|
|
srcWidth,
|
|
srcHeight,
|
|
];
|
|
|
|
// Avoid "IndexSizeError"
|
|
if (dstWidth > 0 && dstHeight > 0) {
|
|
const scale = width / initialWidth;
|
|
|
|
params.push(
|
|
dstX * scale,
|
|
dstY * scale,
|
|
dstWidth * scale,
|
|
dstHeight * scale,
|
|
);
|
|
}
|
|
|
|
context.drawImage(source, ...params.map(param => Math.floor(normalizeDecimalNumber(param))));
|
|
|
|
return canvas;
|
|
},
|
|
|
|
/**
|
|
* Change the aspect ratio of the crop box.
|
|
* @param {number} aspectRatio - The new aspect ratio.
|
|
* @returns {Object} this
|
|
*/
|
|
setAspectRatio(aspectRatio) {
|
|
const { options } = this;
|
|
|
|
if (!this.disabled && !isUndefined(aspectRatio)) {
|
|
// 0 -> NaN
|
|
options.aspectRatio = Math.max(0, aspectRatio) || NaN;
|
|
|
|
if (this.ready) {
|
|
this.initCropBox();
|
|
|
|
if (this.cropped) {
|
|
this.renderCropBox();
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Change the drag mode.
|
|
* @param {string} mode - The new drag mode.
|
|
* @returns {Object} this
|
|
*/
|
|
setDragMode(mode) {
|
|
const { options, dragBox, face } = this;
|
|
|
|
if (this.loaded && !this.disabled) {
|
|
const croppable = mode === DRAG_MODE_CROP;
|
|
const movable = options.movable && mode === DRAG_MODE_MOVE;
|
|
|
|
mode = (croppable || movable) ? mode : DRAG_MODE_NONE;
|
|
|
|
setData(dragBox, DATA_ACTION, mode);
|
|
toggleClass(dragBox, CLASS_CROP, croppable);
|
|
toggleClass(dragBox, CLASS_MOVE, movable);
|
|
|
|
if (!options.cropBoxMovable) {
|
|
// Sync drag mode to crop box when it is not movable
|
|
setData(face, DATA_ACTION, mode);
|
|
toggleClass(face, CLASS_CROP, croppable);
|
|
toggleClass(face, CLASS_MOVE, movable);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
};
|