FabricJS rolling back the old object, when i try to add another one

22 Views Asked by At

I'm trying to develop some application in react, that have a Dropzone, that the user can drop or load many images files, and when the user click in the CheckMark Icon, it will update the state of the selectedImage, and pass it to the canvas, to create the canvas and add an new image, but when the user try to click in another checkmark it will update the state, and call a useEffect to add a new Image to the canvas, and its working, but when I try to move, the second image, it just show off from the screen, and the first image back again to the screen, can someone help me? The behavior I want is that when the user click another checkmark it just exclude the previous image and add the new one, i'm using useRef to save the reference and make it.

Obs: My canvas already have a background, that i dont want to be excluded when the user switch images.

I discover that when I use the renderAll() or requestRenderAll(), my render images/objects its returning to initial position, then when I move it again it go back to the last position, why is that?

CustomDropzone.js

import React, { useCallback, useEffect, useState } from "react";
import { useDropzone } from "react-dropzone";
import {
  ArrowUpTrayIcon,
  XMarkIcon,
  CheckIcon,
} from "@heroicons/react/24/solid";

export default function CustomDropzone({ className, setCanvasImage }) {
  const [files, setFiles] = useState([]);
  const [rejected, setRejected] = useState([]);

  const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
    if (acceptedFiles?.length) {
      setFiles((previousFiles) => [
        ...previousFiles,
        ...acceptedFiles.map((file) =>
          Object.assign(file, { preview: URL.createObjectURL(file) })
        ),
      ]);
    }

    if (rejectedFiles?.length) {
      setRejected((previousFiles) => [...previousFiles, ...rejectedFiles]);
    }
  }, []);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    accept: {
      "image/*": [],
    },
    maxSize: 1024 * 10000,
    // maxFiles: 5,
    onDrop,
  });

  useEffect(() => {
    // Revoke the data uris to avoid memory leaks
    return () => files.forEach((file) => URL.revokeObjectURL(file.preview));
  }, [files]);

  const removeFile = (name) => {
    setFiles((files) => files.filter((file) => file.name !== name));
  };

  const removeAll = () => {
    setFiles([]);
    setRejected([]);
  };

  const removeRejected = (name) => {
    setRejected((files) => files.filter(({ file }) => file.name !== name));
  };

  const getCanvasURI = async (file) => {
    try {
      const dataURI = await handleCanvasImage(file);
      setCanvasImage(dataURI);
    } catch (error) {
      console.error("Error reading the file:", error);
    }
  };

  const handleCanvasImage = (file) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.readAsDataURL(file);
      reader.onload = function (e) {
        const dataURI = e.target.result;
        resolve(dataURI);
      };

      reader.onerror = function (error) {
        reject(error);
      };
    });
  };

  return (
    <div className="mt-10">
      <div
        {...getRootProps({
          className: className,
        })}
      >
        <input {...getInputProps()} />
        <div className="flex flex-col items-center justify-center gap-4">
          <ArrowUpTrayIcon className="w-5 h-5 fill-current" />
          {isDragActive ? (
            <p>Solte os arquivos aqui ...</p>
          ) : (
            <p>
              Arraste e solte os arquivos aqui ou clique para selecionar os
              arquivos
            </p>
          )}
        </div>
      </div>

      <section className="mt-10">
        {files.length > 0 && (
          <>
            <div className="flex gap-4">
              <h2 className="title text-3xl font-semibold">Visualização</h2>
              <button
                type="button"
                onClick={removeAll}
                className="mt-1 text-[12px] uppercase tracking-wider font-bold text-neutral-500 border border-secondary-400 rounded-md px-3 hover:bg-secondary-400 hover:text-white transition-colors"
              >
                Remover todos arquivos
              </button>
            </div>

            <h3 className="title text-lg font-semibold text-neutral-600 mt-10 border-b pb-3">
              Arquivos válidos
            </h3>
            <ul className="mt-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-10">
              {files.map((file) => (
                <li
                  key={file.name}
                  className="relative h-32 rounded-md shadow-lg"
                >
                  <img
                    src={file.preview}
                    alt={file.name}
                    width={100}
                    height={100}
                    onLoad={() => {
                      URL.revokeObjectURL(file.preview);
                    }}
                    className="h-full w-full object-contain rounded-md"
                  />
                  <button
                    type="button"
                    className="w-7 h-7 border border-success-400 bg-success-400 rounded-full flex justify-center items-center absolute -top-3 right-6 hover:bg-white transition-colors"
                    onClick={() => getCanvasURI(file)}
                  >
                    <CheckIcon className="w-5 h-5 fill-white hover:fill-success-400 transition-colors" />
                  </button>

                  <button
                    type="button"
                    className="w-7 h-7 border border-secondary-400 bg-secondary-400 rounded-full flex justify-center items-center absolute -top-3 -right-3 hover:bg-white transition-colors"
                    onClick={() => removeFile(file.name)}
                  >
                    <XMarkIcon className="w-5 h-5 fill-white hover:fill-secondary-400 transition-colors" />
                  </button>
                  <p className="mt-2 text-neutral-500 text-[12px] font-medium">
                    {file.name}
                  </p>
                </li>
              ))}
            </ul>
          </>
        )}

        {rejected.length > 0 && (
          <>
            <h3 className="title text-lg font-semibold text-neutral-600 mt-24 border-b pb-3">
              Arquivos inválidos
            </h3>
            <ul className="mt-6 flex flex-col">
              {rejected.map(({ file, errors }) => (
                <li
                  key={file.name}
                  className="flex items-start justify-between"
                >
                  <div>
                    <p className="mt-2 text-neutral-500 text-sm font-medium">
                      {file.name}
                    </p>
                    <ul className="text-[12px] text-red-400">
                      {errors.map((error) => (
                        <li key={error.code}>{error.message}</li>
                      ))}
                    </ul>
                  </div>
                  <button
                    type="button"
                    className="mt-1 py-1 text-[12px] uppercase tracking-wider font-bold text-neutral-500 border border-secondary-400 rounded-md px-3 hover:bg-secondary-400 hover:text-white transition-colors"
                    onClick={() => removeRejected(file.name)}
                  >
                    remover
                  </button>
                </li>
              ))}
            </ul>
          </>
        )}
      </section>
    </div>
  );
}

Canvas.js

import React, { useEffect, useRef } from "react";
import { fabric } from "fabric";

const Canvas = ({ canvasImage }) => {
  const canvasRef = useRef(null);
  const imageRef = useRef(null);

  useEffect(() => {
    const canvas = initCanvas();
    setBackground("/3.png", canvas);
    canvasRef.current = canvas;
  }, []);

  useEffect(() => {
    addImage(canvasRef.current, canvasImage);
  }, [canvasImage]);

  const initCanvas = () => {
    return new fabric.Canvas("canvas", {
      height: 800,
      width: 800,
    });
  };

  const setBackground = (url, canvas) => {
    fabric.Image.fromURL(url, (img) => {
      img.set({
        originX: "center",
        originY: "center",
        left: canvas.width / 2,
        top: canvas.height / 2,
        scaleX: canvas.width / img.width,
        scaleY: canvas.height / img.height,
        repeat: "no-repeat",
        opacity: 0.85,
      });
      canvas.setBackgroundImage(img);
      canvas.requestRenderAll();
    });
  };

  const addImage = (canvas, canvasImgUrl) => {
    fabric.Image.fromURL(canvasImgUrl, (img) => {
      img.set({
        left: canvas.width / 2,
        top: canvas.height / 2,
        scaleX: 1,
        scaleY: 1,
        originX: "center",
        originY: "center",
        globalCompositeOperation: "destination-over",
      });

      const existingImage = imageRef.current;

      if (existingImage) {
        canvas.remove(existingImage);
      }

      canvas.add(img);
      imageRef.current = img;
      canvas.setActiveObject(img);
      canvas.requestRenderAll();
    });
  };

  const restoreCenter = () => {
    const canvas = canvasRef.current;
    const img = imageRef.current;
    if (img) {
      img.left = canvas.width / 2;
      img.top = canvas.height / 2;
      img.setCoords();
    }
    canvas.renderObjects()
  };

  return (
    <div className="mt-12">
      <h1>Modifique aqui sua imagem</h1>
      <canvas id="canvas" />
      <button onClick={() => restoreCenter()}>Resetar</button>
    </div>
  );
};

export default Canvas;

App.js

import { useState } from "react";
import "./App.css";
import Canvas from "./components/Canvas";
import CustomDropzone from "./components/CustomDropzone";

function App() {
  const [canvasImage, setCanvasImage] = useState(null);

  return (
    <div>
      <CustomDropzone setCanvasImage={setCanvasImage} />
      {canvasImage && <Canvas canvasImage={canvasImage} />}
    </div>
  );
}

export default App;

I'm trying to solve this problem of the new object when added and try to move it, just shows off and appear the previous image.

0

There are 0 best solutions below