`I want to make a trim and clip path functionality with fabric js, basically the same thing happens to me as in this case https://stackoverflow.com/questions/75260621/image-cropping-and-masking-using-fabricjs/76327881 I would greatly appreciate any help
The problem is that when reducing the image that contains the clipPath to a small size the element exceeds the limits, if I move the image the clipPath adapts again, but the problem is in the resizing.
import { extension } from '../utils';
import { controlPositionIcons } from './drawControls';
export enum ControlPositions {
TOP_LEFT = 'tl',
TOP = 't',
TOP_RIGHT = 'tr',
RIGHT = 'r',
BOTTOM_RIGHT = 'br',
BOTTOM = 'b',
BOTTOM_LEFT = 'bl',
LEFT = 'l',
}
export default extension('image.controls', (fabric) => {
fabric.util.object.extend(fabric.Image.prototype, {
type: 'image',
_editingMode: false,
__editingImage: null,
cornerLengthEditing: 5,
cornerStrokeColorEditing: 'black',
cornerSizeEditing: 2,
cropType: 'triangle',
initialize: function (element, options) {
options || (options = {});
this.filters = [];
this.cacheKey = 'texture' + fabric.Object.__uid++;
this.registerEditingEvents();
this.callSuper('initialize', options);
this._initElement(element, options);
},
getElement: function () {
return this._element || {};
},
applyCrop: function () {
if (this.clipPath instanceof fabric.Object) {
this.clipPath = null;
}
if (this.cropType === 'circle') {
this.clipPath = new fabric.Ellipse({
rx: this.width / 2,
ry: this.height / 2,
left: -this.width / 2,
top: -this.height / 2,
});
}
if (this.cropType === 'triangle') {
this.clipPath = new fabric.Triangle({
width: this.width,
height: this.height,
left: -this.width / 2,
top: -this.height / 2,
minScaleLimit: 0,
});
}
return this;
},
registerEditingEvents: function () {
this.on('mousedblclick', () => {
if (!this._editingMode) {
return this.enterEditingMode();
} else {
this.exitEditingMode();
}
});
this.on('deselected', () => {
this.exitEditingMode();
});
},
enterEditingMode: function () {
if (this.selectable && this.canvas) {
this._editingMode = true;
this.__editingImage = fabric.util.object.clone(this);
this.__editingImage.clipPath = null;
const element = this.__editingImage.getElement();
const { top = 0, left = 0, cropX = 0, cropY = 0, scaleX = 1, scaleY = 1 } = this.__editingImage;
this.__editingImage.set({
top: top - cropY * scaleY,
left: left - cropX * scaleX,
height: element.height,
width: element.width,
cropX: 0,
cropY: 0,
opacity: 0.6,
selectable: true,
evented: false,
excludeFromExport: true,
});
this.canvas.add(this.__editingImage);
this.controls = this.__editingControls();
this.applyCrop();
this.canvas.requestRenderAll();
this.on('moving', this.__editingOnMoving);
}
},
exitEditingMode: function () {
if (this.selectable && this.canvas) {
this._editingMode = false;
if (this.__editingImage) {
this.canvas.remove(this.__editingImage);
this.__editingImage = null;
}
this.off('moving', this.__editingOnMoving);
this.controls = fabric.Object.prototype.controls;
this.fire('exit:editing', { target: this });
this.canvas.requestRenderAll();
}
},
__editingControls: function () {
const controls = Object.values(ControlPositions);
return controls.map(this.__createEditingControl.bind(this));
},
__createEditingControl: function (position) {
const cursor = position.replace('t', 's').replace('l', 'e').replace('b', 'n').replace('r', 'w');
return new fabric.Control({
cursorStyle: cursor + '-resize',
actionName: `edit_${this.type}`,
render: controlPositionIcons[position],
positionHandler: this.__editingControlPositionHandler.bind(this, position),
actionHandler: this.__editingActionHandlerWrapper(position),
});
},
__editingActionHandlerWrapper: function (position) {
return (_event, _transform, x, y) => {
this.__editingSetCrop(position, x, y, true);
return true;
};
},
__editingOnMoving: function (event) {
if (this._editingMode && event.pointer) {
this.set('dirty', true);
this.__editingSetCrop(ControlPositions.TOP_LEFT, this.left, this.top);
}
},
__editingSetCrop: function (position, x, y, resize = false) {
if (this.__editingImage) {
const { top = 0, left = 0, width = 0, height = 0, scaleX = 1, scaleY = 1 } = this.__editingImage;
if (position.includes('t')) {
const maxTop = top + height * scaleY - (resize ? 0 : this.getScaledHeight());
const minTop = Math.min(y, maxTop, this.top + this.getScaledHeight());
this.top = Math.max(minTop, top);
const cropY = Math.min((Math.min(Math.max(y, top), this.top) - top) / scaleY, height);
if (resize) {
this.height = Math.max(0, Math.min(this.height + (this.cropY - cropY), height));
}
this.cropY = cropY;
} else if (position.includes('b') && resize) {
const minHeight = Math.min((y - top) / scaleY - this.cropY, height - this.cropY);
this.height = Math.max(0, minHeight);
}
if (position.includes('l')) {
const maxLeft = left + width * scaleX - (resize ? 0 : this.getScaledWidth());
const minLeft = Math.min(x, maxLeft, this.left + this.getScaledWidth());
this.left = Math.max(minLeft, left);
const cropX = Math.min((Math.min(Math.max(x, left), this.left) - left) / scaleX, width);
if (resize) {
this.width = Math.max(0, Math.min(this.width + (this.cropX - cropX), width));
}
this.cropX = cropX;
} else if (position.includes('r') && resize) {
const minWidth = Math.min((x - left) / scaleX - this.cropX, width - this.cropX);
this.width = Math.max(0, minWidth);
}
this.applyCrop();
}
},
__editingControlPositionHandler: function (position) {
const xMultiplier = position.includes('l') ? -1 : position.length > 1 || position === 'r' ? 1 : 0;
const yMultiplier = position.includes('t') ? -1 : position.length > 1 || position === 'b' ? 1 : 0;
const x = (this.width / 2) * xMultiplier;
const y = (this.height / 2) * yMultiplier;
return fabric.util.transformPoint(
new fabric.Point(x, y),
fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, this.calcTransformMatrix())
);
},
__renderEditingControl: function (position, ctx, left, top) {
ctx.save();
ctx.strokeStyle = this.cornerStrokeColorEditing;
ctx.lineWidth = this.cornerSizeEditing;
ctx.translate(left, top);
if (this.angle) {
ctx.rotate(fabric.util.degreesToRadians(this.angle));
}
ctx.beginPath();
const x = position.includes('l') ? -ctx.lineWidth : position.includes('r') ? ctx.lineWidth : 0;
const y = position.includes('t') ? -ctx.lineWidth : position.includes('b') ? ctx.lineWidth : 0;
if (position === 'b' || position === 't') {
ctx.moveTo(x - this.cornerLengthEditing / 2, y);
ctx.lineTo(x + this.cornerLengthEditing, y);
} else if (position === 'r' || position === 'l') {
ctx.moveTo(x, y - this.cornerLengthEditing / 2);
ctx.lineTo(x, y + this.cornerLengthEditing);
} else {
if (position.includes('b')) {
ctx.moveTo(x, y - this.cornerLengthEditing);
} else if (position.includes('t')) {
ctx.moveTo(x, y + this.cornerLengthEditing);
}
ctx.lineTo(x, y);
if (position.includes('r')) {
ctx.lineTo(x - this.cornerLengthEditing, y);
} else if (position.includes('l')) {
ctx.lineTo(x + this.cornerLengthEditing, y);
}
}
ctx.stroke();
ctx.restore();`your text`
},
});
});