I have set up Fabric.js
in my react app using the fabric npm package. I am trying to build an art board where you can draw, add text and add images. For some reason adding elements (images/text) seems to break the app. When adding an image the image appears for some time then disappears when some change occurs and re-appears when selecting any other image/text.
The point is while drawing, only drawings are visible and when adding elements, only elements are visible while all of them (objects) are present on the canvas.
I have created a similar environment to simulate the issue, please let me know of any fixes.
NOTE: The main code is in the fabricCanvas.js
file which creates a class to handle all of fabric.js
features.
CODESANBOX LINK (Updated)
I added a check as well to check if the image is present on Canvas by mapping all objects, and it's there but for some reason not visible.
I also tried a solution of removing the strict
mode but it doesn't help.
I have tried different solutions of calling renderAll()
on every update, but that doesn't work as well.
There is many problem with your initialization
using fabricJS with useEffect
first :
<div
id="canvas-container"
ref={containerRef} //use ref on the container to observe his size
style={{ maxWidth: "100vw", overflow: "hidden" }}
>
<canvas id={canvasID} />
</div>
Then define your ref
const containerRef = useRef(null);
const canvasID = "canvas";
Now you can use use Effect hook to run initialization logic when the component mounts
/**
* Effect hook to run initialization logic when the component mounts or
when the container changes.
*/
useEffect(() => {
const container = containerRef.current;
/**
* Checking if the container exists before proceeding with initialization.
*/
if (!container) {
console.error("❌ The container does not exist");
return;
}
/**
* Getting the initial dimensions of the container.
*/
const clientHeight = container.clientHeight;
const clientWidth = container.clientWidth;
/**
* Create the main canvas
*/
fabricCanvas.init(canvasID, clientWidth, clientHeight);
}, []);
At this step the canvas is working. But in react you want to dispose the canvas in case of your component is dismounted. What i suggest is to separate your canvas initialization and your different features. store your canvas into a context or a state manager of your choice
look a this example :
useEffect(() => {
const container = containerRef.current;
/**
* Checking if the container exists before proceeding with initialization.
*/
if (!container) {
console.error("❌ The container does not exist");
return;
}
/**
* Setting the container reference in the fabricStore for future use.
*/
fabricStore.setContainerRef(container);
/**
* Getting the initial dimensions of the container.
*/
const clientHeight = container.clientHeight;
const clientWidth = container.clientWidth;
/**
* Create the main canvas
*/
initCanvas(clientHeight, clientWidth);
// your different config to initialize
initControls();
initWorkspace();
initBackground();
/**
* once the canvas has been created
* your different features
*/
if (fabricStore.canvas) {
new FabricFeature02(fabricStore.canvas);
new FabricFeature03(fabricStore.canvas);
new FabricFeature04(fabricStore.canvas);
new FabricFeature05(fabricStore.canvas);
}
const resizeObserver = new ResizeObserver((entries) => {
const { width = clientWidth, height = clientHeight } =
(entries[0] && entries[0]?.contentRect) || {};
resizeCanvas(width!, height!); // your resize logic
});
// Attaching the ResizeObserver to the container, assuming the container is of type HTMLDivElement.
resizeObserver.observe(container as HTMLDivElement);
/**
* Returning a cleanup function to be executed when the component is unmounted.
*/
return () => {
//Disposing of the canvas using the dispose method, assuming it exists in the fabricStore.
fabricStore.canvas?.dispose();
// Checking if the container element exists before attempting to disconnect the ResizeObserver
if (container) {
// Disconnecting the ResizeObserver to stop observing changes in the container size.
resizeObserver.disconnect();
}
};
}, []);