Search code examples
javascriptreactjscanvasfabricjs

Fabric.js image/text disappears when drawing


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.


Solution

  • 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();
    
            }
        };
    }, []);