Programing

Fabric.js를 React와 함께 사용하는 방법

c10106 2022. 4. 3. 19:41
반응형

Fabric.js를 React와 함께 사용하는 방법

나는 Fabric.js를 통해 HTML5 캔버스를 사용하는 어플리케이션을 가지고 있다.app는 anguage 1.x 위에 쓰여있으며, reaction으로 마이그레이트할 예정이다.내 앱은 텍스트와 그리기 선, 직사각형, 타원을 쓸 수 있다.또한 하나 이상의 그러한 물체를 이동, 확대, 축소, 선택, 절단, 복사, 붙여넣는 것도 가능하다.다양한 바로가기를 사용하여 캔버스를 확대/축소하고 이동할 수도 있다.간단히 말해서, 내 앱은 Fabric.js를 최대한 활용한다.

Fabric.js를 Reaction과 함께 사용하는 방법에 대한 많은 정보를 찾을 수 없었기 때문에, 1.큰 수정 없이 가능한가, 2.Raction을 더 잘 지원하는 다른 광대한 캔버스 라이브러리를 사용해야 할까?

React+Fabric.js의 유일한 예는 react-komik이었는데, 내 앱보다 훨씬 간단하다.나의 주된 관심사는 Fabric.js의 이벤트 처리와 DOM 조작, 그리고 그것들이 Reaction에 미치는 영향이다.

리액션(reaction-canvas)이라고 불리는 리액션을 위한 캔버스 라이브러리도 있는 것 같지만 Fabric.js에 비해 기능이 많이 부족한 것 같다.

React 앱에서 Fabric.js를 사용할 때 고려해야 할 사항(DOM 조작, 이벤트 처리 등)은 무엇인가?

우리는 Fabric.js를 어떻게 반응하는지 앱에서 같은 문제를 가지고 있었다.내가 추천하는 것은 직물을 통제되지 않는 성분으로 취급하는 것이다.전체 앱이 패브릭과 대화하고 패브릭 콜을 할 수 있는 패브릭 인스턴스(instance)를 가지고 있다가 변경 사항이 있을 때 사용.toObject()모든 직물 상태를 Redex 스토어에 넣기 위해 전화하십시오.그런 다음 React 앱은 일반적인 React 앱에서처럼 글로벌 Redex 상태에서 패브릭 상태를 읽을 수 있다.

StackOverflow 코드 편집기에서 작업하는 예는 얻을 수 없지만, 여기 추천하는 패턴을 구현하는 JSFiddle 예가 있다.

개념 증명 프로젝트에 Fabric을 사용했는데 일반적인 아이디어는 예를 들어 D3와 같다. Fabric은 DOM 요소에서 작동하며 React는 데이터를 DOM으로 렌더링하며, 보통 후자는 지연된다는 것을 명심하라.코드가 제대로 작동하는지 확인하는 데 도움이 되는 두 가지가 있다.

구성 요소가 장착될 때까지 대기

그렇게 하려면 패브릭 인스턴스화를componentDidMount:

import React, { Component } from 'react';
import { fabric } from 'react-fabricjs';
import styles from './MyComponent.css';

class MyComponent extends Component {
  componentWillMount() {
    // dispatch some actions if you use Redux
  }

  componentDidMount() {
    const canvas = new fabric.Canvas('c');

    // do some stuff with it
  }

  render() {
    return (
      <div className={styles.myComponent}>
        <canvas id="c" />
      </div>
    )
  }
}

Fabric 생성자를 다음 위치에 배치하는 중componentDidMount이 방법을 실행하는 순간에는 DOM이 준비되기 때문에 실패하지 않도록 보장한다. (하지만 Redex를 사용할 경우 소품이 준비되지 않을 때도 있음)

참조를 사용하여 실제 너비와 높이 계산

참조는 실제 DOM 요소에 대한 참조다.DOM API를 사용하여 DOM 요소를 사용하여 수행할 수 있는 작업 참조: 자식 선택, 부모 찾기, 스타일 속성 할당, 계산innerHeight그리고innerWidth로 하는 것이다 후자는 정확히 필요한 것이다.

componentDidMount() {
  const canvas = new fabric.Canvas('c', {
    width: this.refs.canvas.clientWidth,
    height: this.refs.canvas.clientHeight
  });

  // do some stuff with it
}

잊지 말고 정의를 내리십시오.refsthis그러기 위해서는 건설업자가 필요할 것이다.모든 마마처럼 이다.

import React, { Component } from 'react';
import { fabric } from 'react-fabricjs';
import styles from './MyComponent.css';

class MyComponent extends Component {
  constructor() {
    super()
    this.refs = {
      canvas: {}
    };
  }

  componentWillMount() {
    // dispatch some actions if you use Redux
  }

  componentDidMount() {
    const canvas = new fabric.Canvas('c', {
      width: this.refs.canvas.clientWidth,
      height: this.refs.canvas.clientHeight
    });

    // do some stuff with it
  }

  render() {
    return (
      <div className={styles.myComponent}>
        <canvas
          id="c"
          ref={node => {
            this.refs.canvas = node;
          } />
      </div>
    )
  }
}

Fabric과 구성 요소 상태 또는 소품 혼합

패브릭 인스턴스가 모든 구성 요소 또는 상태 업데이트에 반응하도록 할 수 있다.Fabric 인스턴스를 업데이트하십시오(보시다시피 구성 요소 자체의 속성의 일부로 저장할 수 있음).componentDidUpdate. 단순히 에 의존하는 것render기능 호출은 실제로 도움이 되지 않을 것이다. 왜냐하면 렌더링되는 요소들 중 어떤 것도 새로운 소품이나 새로운 상태에 대해 변하지 않을 것이기 때문이다.이와 비슷한 것:

import React, { Component } from 'react';
import { fabric } from 'react-fabricjs';
import styles from './MyComponent.css';

class MyComponent extends Component {
  constructor() {
    this.refs = {
      canvas: {}
    };
  }

  componentWillMount() {
    // dispatch some actions if you use Redux
  }

  componentDidMount() {
    const canvas = new fabric.Canvas('c', {
      width: this.refs.canvas.clientWidth,
      height: this.refs.canvas.clientHeight
    });

    this.fabric = canvas;

    // do some initial stuff with it
  }

  componentDidUpdate() {
    const {
      images = []
    } = this.props;
    const {
      fabric
    } = this;

    // do some stuff as new props or state have been received aka component did update
    images.map((image, index) => {
      fabric.Image.fromURL(image.url, {
        top: 0,
        left: index * 100 // place a new image left to right, every 100px
      });
    });
  }

  render() {
    return (
      <div className={styles.myComponent}>
        <canvas
          id="c"
          ref={node => {
            this.refs.canvas = node;
          } />
      </div>
    )
  }
}

새로운 구성 요소 상태 또는 소품에 따라 달라지는 필요한 코드로 이미지 렌더링을 대체하십시오.캔버스에 새 개체를 렌더링하기 전에 캔버스 청소도 잊지 마십시오!

리액션 16.8 이상을 사용하여 사용자 정의 후크를 만들 수도 있다.

import React, { useRef, useCallback } from 'react';
const useFabric = (onChange) => {
    const fabricRef = useRef();
    const disposeRef = useRef();
    return useCallback((node) => {
        if (node) {
            fabricRef.current = new fabric.Canvas(node);
            if (onChange) {
                disposeRef.current = onChange(fabricRef.current);
            }
        }
        else if (fabricRef.current) {
            fabricRef.current.dispose();
            if (disposeRef.current) {
                disposeRef.current();
                disposeRef.current = undefined;
            }
        }
    }, []);
};

사용법

const FabricDemo = () => {
  const ref = useFabric((fabricCanvas) => {
    console.log(fabricCanvas)
  });
  return <div style={{border: '1px solid red'}}>
    <canvas ref={ref} width={300} height={200} />
  </div>
}

여기 리액션의 기능성 부품을 사용한 해답은 하나도 보이지 않고 @jantimon의 후크도 제대로 작동할 수 없었다.여기 내 접근법이 있다.나는 캔버스 자체가 페이지의 레이아웃에서 치수를 취하게 한 다음, (0,0)에서 사용자의 그리기 가능한 영역 역할을 하는 "배경 객체"를 갖게 한다.

import React, { useState, useRef, useEffect } from 'react';

const { fabric } = window;

// this component takes one prop, data, which is an object with data.sizePixels, an array that represents the width,height of the "backgroundObject"

const CanvasComponent = ({ data }) => {
  const canvasContainer = useRef();
  const canvasRef = useRef();
  const fabricRef = useRef();
  const [error, setError] = useState();

  const initCanvas = ({ canvas, size }) => {
    console.log('*** canvas init');
    console.log(canvas);

    let rect = new fabric.Rect({
      width: size[0],
      height: size[1],
      fill: 'white',
      left: 0,
      top: 0,
      selectable: false,
      excludeFromExport: true,
    });
    canvas.add(rect);

    rect = new fabric.Rect({
      width: 100,
      height: 100,
      fill: 'red',
      left: 100,
      top: 100,
    });
    canvas.add(rect);
  };

  // INIT
  useEffect(() => {
    if (!canvasRef.current) return;
    if (fabricRef.current) return;
    const canvas = new fabric.Canvas('canvas', {
      width: canvasContainer.current.clientWidth,
      height: canvasContainer.current.clientHeight,
      selection: false, // disables drag-to-select
      backgroundColor: 'lightGrey',
    });
    fabricRef.current = canvas;
    initCanvas({ canvas, size: data.sizePixels });
  });

  // INPUT CHECKING
  if (!data || !Array.isArray(data.sizePixels)) setError('Sorry, we could not open this item.');

  if (error) {
    return (
      <p>{error}</p>
    );
  }

  return (
    <>
      <div style={{
        display: 'flex',
        flexDirection: 'row',
        width: '100%',
        minHeight: '60vh',
      }}
      >
        <div style={{ flex: 3, backgroundColor: 'green' }}>
          Controls
        </div>
        <div ref={canvasContainer} style={{ flex: 7, backgroundColor: 'white' }}>
          <canvas id="canvas" ref={canvasRef} style={{ border: '1px solid red' }} />
        </div>
      </div>

      <div>
        <button
          type="button"
          onClick={() => {
            const json = fabricRef.current.toDatalessJSON();
            setDebugJSON(JSON.stringify(json, null, 2));
          }}
        >
          Make JSON
        </button>
      </div>
    </>
  );
};

export default CanvasComponent;

원단 객체를 반응 구성요소로 사용할 수 있는 react-fabricjs 패키지도 있다.사실 리샤트의 답변에는 이 패키지가 포함되어 있지만, 리액트-fabricjs에는'fabric'개체가 없기 때문에 어떻게 작동해야 하는지 이해할 수 없다(그는아마도 'fabric-webpack의 package를 의미했을 것이다).간단한 'Hello world' 구성 요소의 예:

import React from 'react';
import {Canvas, Text} from 'react-fabricjs';

const HelloFabric = React.createClass({
  render: function() {
    return (
      <Canvas
        width="900"
        height="900">
          <Text
            text="Hello World!"
            left={300}
            top={300}
            fill="#000000"
            fontFamily="Arial"
          />
      </Canvas>
    );
  }
});

export default HelloFabric;

이 패키지를 사용하지 않으려는 경우에도 코드를 탐색하면 Resact by react에서 Fabric.js를 구현하는 방법을 이해하는 데 도움이 될 수 있다.

나는 StefanHayden의 대답을 따랐고 여기 시험 코드가 있다.

패브릭 캔버스 객체 작성

fabricRef(Callback Ref)를 반환하는 사용자 지정 후크 만들기:

const useFabric = () => {
  const canvas = React.createRef(null);
  const fabricRef = React.useCallback((element) => {
    if (!element) return canvas.current?.dispose();

    canvas.current = new fabric.Canvas(element, {backgroundColor: '#eee'});
    canvas.current.add(new fabric.Rect(
      {top: 100, left: 100, width: 100, height: 100, fill: 'red'}
    ));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return fabricRef;
};

참고:

마지막으로 캔버스를 만들고 참조를 전달하십시오.

function App() {
  const fabricRef = useFabric();
  return <canvas ref={fabricRef} width={640} height={360}/>;
}

하지만 다른 곳에서는 패브릭캔바스를 사용할 수 없어, 다음 장에서 설명하겠다.

코데펜

컨텍스트별로 패브릭 캔버스 개체 공유

Refs 변이 가능한 을 저장하는 컨텍스트에 의해 우리는 어디서든 패브릭 캔버스 오브젝트를 사용할 수 있다.

첫째, 참조를 값으로 삼는 컨텍스트를 정의한다.

const FabricContext = React.createContext();

function App() {
  return (
    <FabricContext.Provider value={React.createRef()}>
      <MyToolKit />
      <MyFabric />
    </FabricContext.Provider>
  );
}

그러면, 우리는 그것을 사용자 정의 후크에 사용할 수 있어.지금은 참조를 작성하지 않지만, 컨텍스트에서 참조를 사용하십시오.

const useFabric = () => {
  const canvas = React.useContext(FabricContext);
  const fabricRef = React.useCallback((element) => {
    if (!element) return canvas.current?.dispose();

    canvas.current = new fabric.Canvas(element, {backgroundColor: '#eee'});
    canvas.current.add(new fabric.Rect(
      {top: 100, left: 100, width: 100, height: 100, fill: 'red'}
    ));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return fabricRef;
};

function MyFabric() {
  const fabricRef = useFabric();
  return <canvas ref={fabricRef} width={640} height={360} />;
}

또한 컨텍스트를 사용할 수 있는 경우에만 어디에서나 사용할 수 있다.

function MyToolKit() {
  const canvas = React.useContext(FabricContext);
  const drawRect = () => {
    canvas.current?.add(new fabric.Rect(
      {top: 100, left: 100, width: 100, height: 100, fill: 'red'}
    ));
  };
  return <button onClick={drawRect}>Draw</button>;
}

앱의 라이프사이클 전체에 걸쳐 패브릭 캔버스 오브젝트를 이제 어디서나 사용할 수 있다.

코데펜

소품별로 패브릭 캔버스 객체 공유

글로벌 변수처럼 컨텍스트에 의한 공유를 원하지 않는다면 소품별로 부모와 공유할 수도 있다.

코드펜

Fabric Canvas 개체 forwardRef를 통해 공유

글로벌 변수처럼 컨텍스트별로 공유하지 않으려면 ForwardRef를 통해 부모와도 공유하십시오.

코드펜

참조URL: https://stackoverflow.com/questions/37565041/how-can-i-use-fabric-js-with-react

반응형