Search code examples
reactjsimagekonva

I tried to load a blob data in React Konva Image, but in vain because


What do I want to do:

I want to load base64 of a PNG/JPG image file, or blob stored in a db into React Konva Image.

Conditions and observations:

  • The default image is a local image (included in source code) that loads without problem. This image is used as a place holder image.
  • When the actual image blob needs to replace the default one, it appears it will not load.

Things I did:

I used example from https://konvajs.org/docs/react/Images.html

Putting in the line: this.imageNode.getLayer().batchDraw(); should have done some change.

Code:

    import React, { useEffect, useRef, useState, useCallback, Fragment } from "react";
    import { Image as KonvaImage } from "react-konva" // , Transformer
    
    const KImage = ({ properties, isSelected, onSelect, onChange }) => {
    
        // const shapeRef = useRef();
        // const trRef = useRef();
        const imageNode = useRef()
        const [image, setImage] = useState(null)
        const [runOnce, setRunOnce] = useState(false)
    
        const handleLoadImage = useCallback(() => {
            // {
            //     // const { x, y, image } = properties
            //     /* console.log("Image", `loadImage: properties {x, y, image.length}: 
            // ${x}, ${y}, ${image.length}`) */
            // }
            let image = new window.Image();
            image.src = properties.image;
            image.addEventListener('load', () => {
                // console.log("Image", `handleLoad image (loaded): ${image}`)
                setImage(image)
                setRunOnce(false)
                if (imageNode && imageNode.current && imageNode.current.getLayer()) {
                    // console.log("Image", `batchDraw: ${image}`)
                    imageNode.current.getLayer().batchDraw();
                }
            });
        }, [properties])
    
        useEffect(() => {
            // console.log("Image", `useEffect image: ${image}`)
            // console.log("Image", `useEffect: properties.image.length: ${properties.image.length})`)
    
            if (isSelected) {
                // trRef.current.nodes([shapeRef.current]);
                // trRef.current.getLayer().batchDraw();
            }
            if (runOnce) {
                return
            }
            if (!image) {
                handleLoadImage()
                setRunOnce(true)
            }
        }, [isSelected, image, runOnce, handleLoadImage, properties]);
    
        return (
            <Fragment>
    
                <KonvaImage
                    ref={imageNode}
                    onClick={onSelect}
                    onTap={onSelect}
                    x={properties.x}
                    y={properties.y}
                    image={image}
                    draggable
                    onDragEnd={e => {
                        /* onChange({
                           ...points,
                           x: e.target.x(),
                           y: e.target.y(),
                         });*/
                    }}
                    onTransformEnd={e => {
                        // transformer is changing scale
                        /* const node = shapeRef.current;
                         const scaleX = node.scaleX();
                         const scaleY = node.scaleY();
                         node.scaleX(1);
                         node.scaleY(1);
                         onChange({
                           ...points,
                           x: node.x(),
                           y: node.y(),
                           width: node.width() * scaleX,
                           height: node.height() * scaleY,
                         });*/
                    }}
                />
                {/* {isSelected && <Transformer ref={trRef} />} */}
            </Fragment>
        )
    }
    export default KImage

Solution

  • I took the advantage of the useState hook to reset the image to its initial stage. It is working now on both the image path and base64 data.

    In order to reset the image I certainly had to write the code to see whether there is a need to change the image by comparing the data length and the image name.

    Now if I were having more than one image, I would have used the map of image metadata and would have manipulated it to destroy and recreate Konva Image components externally based on the map list.

    code:

    import React, { useEffect, useRef, useState, useCallback, Fragment } from "react";
    import { Image as KonvaImage } from "react-konva" // , Transformer
    
    const KImage = ({ properties, isSelected, onSelect, onChange }) => {
    
        // const shapeRef = useRef();
        // const trRef = useRef();
        const imageNode = useRef()
        const [image, setImage] = useState(null)
        const [runOnce, setRunOnce] = useState(false)
        const [storedProperty, setStoredProperty] = useState(null)
    
        const checkImageChanged = useCallback(() => {
            const { image, name } = properties
            let changedName = name
            let changedLength = image.length
            let storedName, storedLength
            if (storedProperty) {
                let { image, name } = storedProperty
                storedName = name
                storedLength = image.length
            }
    
            if (storedName !== changedName || storedLength !== changedLength) {
                /*  console.log("Image", `checkImageChanged: properties { storedLength, changedLength}: 
               ${storedLength}, ${changedLength}`) */
                //  console.log("Image", `checkImageChanged: storedName ${storedName}: changedName: ${changedName}}`)
                //  console.log("Image", `checkImageChanged: properties { name, image }: ${name}\n${image}`)
                /* if (imageNode && imageNode.current && imageNode.current.getLayer()) {
                    console.log("Image", `checkImageChanged batchDraw`)
                    imageNode.current.getLayer().batchDraw();
                } */
                console.log("Image", `checkImageChanged setRunOnce(false), setImage(null)`)
                setRunOnce(false)
                setImage(null)
            }
        }, [properties, storedProperty])
        // 
        const handleLoadImage = useCallback(() => {
            let image1 = new window.Image();
            image1.src = properties.image;
            image1.addEventListener('load', () => {
                setImage(image1)
                // setRunOnce(false)
                console.log("Image", `handleLoadImage loaded: ${image1}`)
                if (imageNode && imageNode.current && imageNode.current.getLayer()) {
                    console.log("Image", `handleLoadImage batchDraw`)
                    imageNode.current.getLayer().batchDraw();
                }
            });
        }, [properties])
    
        useEffect(() => {
            console.log("Image", `useEffect runOnce: ${runOnce}`)
            console.log("Image", `useEffect image: ${image}`)
            if (isSelected) {
                // trRef.current.nodes([shapeRef.current]);
                // trRef.current.getLayer().batchDraw();
            }
    
            setStoredProperty(properties)
            if (!image) {
                if (runOnce) {
                    return
                }
                handleLoadImage()
                setRunOnce(true)
            }
    
            checkImageChanged()
        }, [isSelected, image, runOnce, checkImageChanged, handleLoadImage, properties]);
        // render
        return (
            <Fragment>
                <KonvaImage
                    ref={imageNode}
                    onClick={onSelect}
                    onTap={onSelect}
                    name={properties.name}
                    x={properties.x}
                    y={properties.y}
                    image={image}
                // draggable
                // onDragEnd={e => {
                //     /* onChange({
                //        ...points,
                //        x: e.target.x(),
                //        y: e.target.y(),
                //      });*/
                // }}
                // onTransformEnd={e => {
                //     // transformer is changing scale
                //     /* const node = shapeRef.current;
                //      const scaleX = node.scaleX();
                //      const scaleY = node.scaleY();
                //      node.scaleX(1);
                //      node.scaleY(1);
                //      onChange({
                //        ...points,
                //        x: node.x(),
                //        y: node.y(),
                //        width: node.width() * scaleX,
                //        height: node.height() * scaleY,
                //      });*/
                // }}
                />
                {/* {isSelected && <Transformer ref={trRef} />} */}
            </Fragment>
        )
    }
    export default KImage