I've found a solution. Im using a circle as a clip path for the image, and then adding a slightly bigger transparent circle for the border. I ended up not having to use the globalCompositeOperation
thanks to the clipPath
and had to set preserveObjectStacking: true
on the canvas to prevent the image (only selectable component) from jumping to the front of the stack.
/** Initialize base canvas */
const initBaseCanvas = (imageSize) => {
const container = document.getElementById("customizer-container");
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
// Create base canvas
const initCanvas = new fabric.Canvas("base-image", {
width: containerWidth,
height: containerHeight,
selectable: false,
evented: false,
allowTouchScrolling: true,
backgroundColor: "transparent",
preserveObjectStacking: true, // Need this to not bring uploaded image to front when moving
});
// Create the image boundary
const circle = new fabric.Circle({
radius: imageSize / 2,
backgroundColor: "transparent",
fill: "#f9f9f9",
selectable: false,
evented: false,
absolutePositioned: true,
});
initCanvas.add(circle);
initCanvas.centerObject(circle);
// Insert uploaded image in the center of the circle and pre-select
const image = new fabric.Image();
image.clipPath = circle;
initCanvas.add(image);
image.setSrc(URL.createObjectURL(uploadedFile), (img) => {
// Scale image down if bigger than canvas to ensure bounding box is visible
const imgWidht = img.width;
if (!imgWidht || imgWidht >= containerWidth) {
img.scaleToWidth(containerWidth - 50);
}
initCanvas.centerObject(img);
initCanvas.setActiveObject(img);
// Colored border
const circle2 = new fabric.Circle({
radius: imageSize / 2 + 1,
stroke: "#fd219b",
fill: "transparent",
strokeWidth: 2,
selectable: false,
evented: false,
});
initCanvas.add(circle2);
initCanvas.centerObject(circle2);
initCanvas.getObjects()[2].bringToFront();
initCanvas.renderAll();
});
return initCanvas;
};