在给定的fabricjs代码中,我正在使用图像控制处理程序开发裁剪和缩放功能。当图像第一次缩放然后裁剪时,在这种情况下它工作正常。然后我进一步缩放裁剪后的图像,然后尝试裁剪此状态,图像状态设置为原始状态。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image crop</title>
<script src="https://unpkg.com/fabric@latest/dist/fabric.js"></script>
</head>
<body>
<canvas id="canvas" width="1200" height="700"></canvas>
<!-- <script src="app.js"></script> -->
</body>
<script>
fabric.Object.prototype.cornerStyle = "circle";
fabric.util.requestAnimFrame(render);
const canvas = new fabric.Canvas("canvas");
let aspectRatioHorizontal;
let aspectRatioVertical;
const UserImage = fabric.util.createClass(fabric.Image, {
type: "userImage",
disableCrop: false,
clipPosition: null,
previousTarget: null,
cropWidth: 0,
cropHeight: 0,
changeWidth: 0,
changeHeight: 0,
rightChange: 0,
bottomChange: 0,
initialize(element, options) {
options = options || {};
options = Object.assign({
cropHeight: this.height,
cropWidth: this.width
}, options);
if (!("clipPosition" in options) || Object.values(fabric.UserImage.CLIP_POSITIONS).indexOf(options.clipPosition) === -1) {
options.clipPosition = fabric.UserImage.CLIP_POSITIONS.CENTER_MIDDLE;
}
this.callSuper("initialize", element, options);
if (!this.disableCrop) {
this.applyCrop();
}
},
getCrop(image, size, scaleX, scaleY, trim_from, corner, target) {
let width = size.width;
let height = size.height;
// Calculate the crop width and height based on the provided size
console.log('scaleX', scaleX, 'scaleY', scaleY)
let x = this.cropX;
let y = this.cropY;
let newWidth = Math.min(width, image.width * scaleX);
let newHeight = Math.min(height, image.height * scaleY);
if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.LEFT_TOP) {
x = this.cropX;
y = this.cropY;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.LEFT_MIDDLE) {
x = this.cropX;
y = this.cropY;
if(this.previousTarget){
x = target.cropX;
y = target.cropY;
}
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.LEFT_BOTTOM) {
x = 0;
y = image.height - newHeight;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.CENTER_TOP) {
x = this.cropX + this.cropWidth - newWidth;
y = this.cropY;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.CENTER_MIDDLE) {
//console.log("thisx ..................", this)
x = (image.width * this.scaleX - newWidth)/2;
y = (image.height * this.scaleY - newHeight)/2;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.CENTER_BOTTOM) {
x = this.cropX + this.cropWidth - newWidth;
y = this.cropY + this.cropHeight - newHeight;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.RIGHT_TOP) {
x = image.width - newWidth;
y = 0;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.RIGHT_MIDDLE) {
x = this.cropX + this.cropWidth - newWidth;
y = this.cropY + this.cropHeight - newHeight;
} else if (this.clipPosition === fabric.UserImage.CLIP_POSITIONS.RIGHT_BOTTOM) {
x = image.width - newWidth;
y = image.height - newHeight;
} else {
console.error(
new Error("Unknown clip position property - " + this.clipPosition)
);
}
const scaleToFitWidth = width / this.getBoundingRect().width;
const scaleToFitHeight = height / this.getBoundingRect().height;
const scale = Math.max(scaleToFitWidth, scaleToFitHeight);
newWidth *= scale;
newHeight *= scale;
return {
cropX: x,
cropY: y,
cropWidth: newWidth,
cropHeight: newHeight,
};
},
applyCrop(c_position, target) {
this.clipPosition = c_position || 'center-middle';
let trim_from;
if (
c_position === fabric.UserImage.CLIP_POSITIONS.CENTER_TOP
|| c_position === fabric.UserImage.CLIP_POSITIONS.CENTER_BOTTOM
) {
trim_from = 'height';
} else if (
c_position === fabric.UserImage.CLIP_POSITIONS.LEFT_MIDDLE
|| c_position === fabric.UserImage.CLIP_POSITIONS.RIGHT_MIDDLE
) {
trim_from = 'width';
}
if (this.disableCrop) return;
let boudingRect = this.getBoundingRect();
const crop = this.getCrop(
this.getOriginalSize(),
{
width: this.getScaledWidth(),
height: this.getScaledHeight(),
},
this.scaleX,
this.scaleY,
trim_from,
c_position,
this.previousTarget
);
if (target && c_position === 'center-middle') {
// Use the previous state for subsequent crops
crop.width = target.width;
crop.height = target.height;
crop.cropWidth = target.cropWidth;
crop.cropHeight = target.cropHeight;
crop.cropX = target.cropX;
crop.cropY = target.cropY;
crop.scaleX = target.scaleX;
crop.scaleY = target.scaleY;
this.previousTarget = crop;
} else {
// Clear the previous state if it's not a 'center-middle' crop
this.previousTarget = null;
}
this.set(crop);
this.setCoords();
console.log('this x------------', this)
canvas.renderAll();
},
_render(ctx) {
if (this.disableCrop) {
this.cropX = 0;
this.cropY = 0;
this.callSuper("_render", ctx);
return;
}
const width = this.width,
height = this.height,
cropWidth = this.cropWidth,
cropHeight = this.cropHeight,
cropX = this.cropX,
cropY = this.cropY;
ctx.save();
ctx.drawImage(
this._element,
cropX,
cropY,
cropWidth,
cropHeight,
-width / 2,
-height / 2,
width,
height
);
ctx.restore();
},
toObject(options) {
options = options || [];
return this.callSuper("toObject", [].concat(Array.from(options), ["cropWidth", "cropHeight", "disableCrop"]));
}
});
UserImage.CLIP_POSITIONS = {
LEFT_TOP: "left-top",
LEFT_MIDDLE: "left-middle",
LEFT_BOTTOM: "left-bottom",
CENTER_TOP: "center-top",
CENTER_MIDDLE: "center-middle",
CENTER_BOTTOM: "center-bottom",
RIGHT_TOP: "right-top",
RIGHT_MIDDLE: "right-middle",
RIGHT_BOTTOM: "right-bottom"
}
UserImage.fromObject = function (object, callback) {
fabric.util.loadImage(object.src, function (img, error) {
if (error) {
callback && callback(null, error);
return;
}
fabric.Image.prototype._initFilters.call(object, object.filters, function (filters) {
object.filters = filters || [];
fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function (resizeFilters) {
object.resizeFilter = resizeFilters[0];
const image = new fabric.UserImage(img, object);
callback(image);
});
});
}, null, object.crossOrigin);
};
fabric.UserImage = UserImage;
fabric.UserImage.async = true;
const controlsUtils = fabric.controlsUtils,
scaleSkewStyleHandler = controlsUtils.scaleSkewCursorStyleHandler,
scaleStyleHandler = controlsUtils.scaleCursorStyleHandler,
scalingEqually = controlsUtils.scalingEqually,
scalingYOrSkewingX = controlsUtils.scalingYOrSkewingX,
scalingXOrSkewingY = controlsUtils.scalingXOrSkewingY,
scaleOrSkewActionName = controlsUtils.scaleOrSkewActionName,
scalingX = controlsUtils.scalingX,
scalingY = controlsUtils.scalingY;
function actionScalingOrSkewingCropHandler(eventData, transform, x, y) {
//console.log('control utils', controlsUtils)
const { target, corner } = transform;
let clip_position = UserImage.CLIP_POSITIONS.CENTER;
if (corner === 'mt') {
clip_position = UserImage.CLIP_POSITIONS.CENTER_BOTTOM
} else if (corner === 'mb') {
clip_position = UserImage.CLIP_POSITIONS.CENTER_TOP
} else if (corner === 'ml') {
clip_position = UserImage.CLIP_POSITIONS.RIGHT_MIDDLE
} else if (corner === 'mr') {
clip_position = UserImage.CLIP_POSITIONS.LEFT_MIDDLE
}
let act;
target.applyCrop(clip_position);
if (corner === "mr" || corner === "ml") {
act = scalingXOrSkewingY(eventData, transform, x, y);
}
if (corner === "mt" || corner === "mb") {
act = scalingYOrSkewingX(eventData, transform, x, y);
}
return act;
}
function actionScalingEquallyCropHandler(eventData, transform, x, y) {
const { target, corner } = transform;
if (["tl", "tr", "bl", "br"].indexOf(corner) > -1 && eventData.shiftKey) {
console.log("center-middle...................")
target.applyCrop();
}
const currentWidth = target.width * target.scaleX;
const currentHeight = target.height * target.scaleY;
const newWidth = target.getScaledWidth(); // Calculate the new width based on scaling
const scaleFactor = newWidth / currentWidth; // Calculate the scale factor
const cropWidth = target.cropWidth / scaleFactor;
const cropHeight = target.cropHeight / scaleFactor;
const cropX = target.cropX + (target.cropWidth - cropWidth) / 2;
const cropY = target.cropY + (target.cropHeight - cropHeight) / 2;
target.set({
width: currentWidth,
height: currentWidth / scaleFactor,
cropWidth,
cropHeight,
cropX,
cropY,
});
target.applyCrop('center-middle', target);
// Apply the updated crop
const action = scalingEqually(eventData, transform, x, y); // Apply scaling action
return action;
}
function render(shadowOffsetX, shadowOffsetY, fn) {
const angle = this.angle;
return function (ctx, left, top, styleOverride, fabricObject) {
if (fabricObject.disableCrop) {
return controlsUtils.renderCircleControl(ctx, left, top, styleOverride, fabricObject);
}
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(angle + fabricObject.angle));
ctx.shadowColor = "rgba(0, 0, 0, 0.7)";
ctx.shadowBlur = 2;
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
ctx.beginPath();
ctx.lineWidth = 6;
ctx.lineCap = "round";
ctx.strokeStyle = "#FFFFFF";
fn.call(this, ctx)
ctx.stroke();
ctx.restore();
}
}
function renderLeftOrRight(ctx, left, top, styleOverride, fabricObject) {
render.call(this, 0, 0, function (ctx) {
ctx.moveTo(0, -8);
ctx.lineTo(0, 8);
})(ctx, left, top, styleOverride, fabricObject);
}
function renderTopOrBottom(ctx, left, top, styleOverride, fabricObject) {
render.call(this, 0, 0, function (ctx) {
ctx.moveTo(-8, 0);
ctx.lineTo(8, 0);
})(ctx, left, top, styleOverride, fabricObject);
}
const userImageControls = {
mr: new fabric.Control({
x: 0.5,
y: 0,
cursorStyleHandler: scaleSkewStyleHandler,
getActionName: scaleOrSkewActionName,
actionHandler: actionScalingOrSkewingCropHandler,
render: renderLeftOrRight
}),
ml: new fabric.Control({
x: -0.5,
y: 0,
cursorStyleHandler: scaleSkewStyleHandler,
getActionName: scaleOrSkewActionName,
actionHandler: actionScalingOrSkewingCropHandler,
render: renderLeftOrRight
}),
mt: new fabric.Control({
x: 0,
y: -0.5,
cursorStyleHandler: scaleSkewStyleHandler,
getActionName: scaleOrSkewActionName,
actionHandler: actionScalingOrSkewingCropHandler,
render: renderTopOrBottom
}),
mb: new fabric.Control({
x: 0,
y: 0.5,
cursorStyleHandler: scaleSkewStyleHandler,
getActionName: scaleOrSkewActionName,
actionHandler: actionScalingOrSkewingCropHandler,
render: renderTopOrBottom
}),
tl: new fabric.Control({
x: -0.5,
y: -0.5,
cursorStyleHandler: scaleStyleHandler,
actionHandler: actionScalingEquallyCropHandler
}),
tr: new fabric.Control({
x: 0.5,
y: -0.5,
cursorStyleHandler: scaleStyleHandler,
actionHandler: actionScalingEquallyCropHandler
}),
bl: new fabric.Control({
x: -0.5,
y: 0.5,
cursorStyleHandler: scaleStyleHandler,
actionHandler: actionScalingEquallyCropHandler
}),
br: new fabric.Control({
x: 0.5,
y: 0.5,
cursorStyleHandler: scaleStyleHandler,
actionHandler: actionScalingEquallyCropHandler
}),
mtr: new fabric.Control({
x: 0,
y: -0.5,
actionHandler: controlsUtils.rotationWithSnapping,
cursorStyleHandler: controlsUtils.rotationStyleHandler,
offsetY: -40,
withConnection: true,
actionName: 'rotate',
})
}
fabric.UserImage.prototype.controls = userImageControls;
let imgObj = new Image(), image = null;
imgObj.crossOrigin = "anonymous"
imgObj.src = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR6JavL8o0BLx1-BXc74ndZdPkLKRCPYQGibA&usqp=CAU";
imgObj.onload = () => {
image = new fabric.UserImage(imgObj, { left: 10, top: 50 });
canvas.add(image);
}
const stateCrop = {}
</script>
</html>
我尝试在图像缩放到 applyCrop 函数时传递目标对象,以便下次操作时它应该是状态。
我实现了一个裁剪工具,通过原生dom而不是fabric.js实现。您可以尝试一下,看看是否满足您的需求。 https://github.com/zxf9397/image-cropper
npm i icropper
import { fabric } from 'fabric';
import { FabricCropListener } from 'icropper';
const cropButton = document.querySelector('#crop-btn') as HTMLButtonElement;
const confirmButton = document.querySelector('#confirm-btn') as HTMLButtonElement;
const cancelButton = document.querySelector('#cancel-btn') as HTMLButtonElement;
(async () => {
const canvas = document.querySelector('#canvas') as HTMLCanvasElement;
canvas.width = 600;
canvas.height = 600;
canvas.style.setProperty('border', `${2}px solid`);
const fabricCanvas = new fabric.Canvas(canvas);
function fabricImageFromURL(url: string, imgOptions?: fabric.IImageOptions) {
return new Promise<fabric.Image>((resolve, reject) => {
try {
fabric.Image.fromURL(url, resolve, { ...imgOptions, crossOrigin: 'anonymous' });
} catch (error) {
reject(error);
}
});
}
const image = (await fabricImageFromURL('/gallery/pic.png', {
left: 100,
top: 100,
scaleX: -0.6,
scaleY: -0.5,
angle: 0,
flipX: true,
flipY: true,
})) as Required<fabric.Image>;
fabricCanvas.add(image).renderAll();
const listener = new FabricCropListener(fabricCanvas, { containerOffsetX: 604, containerOffsetY: 2 });
cropButton.addEventListener('click', listener.crop.bind(listener));
confirmButton.addEventListener('click', listener.confirm.bind(listener));
cancelButton.addEventListener('click', listener.cancel.bind(listener));
})();