web-dev-qa-db-fra.com

Comment puis-je utiliser Fabric.js avec React?

J'ai une application qui utilise beaucoup de toile HTML5 via Fabric.js . L'application est écrite au-dessus de Angular 1.x, et je prévois de la migrer vers React . Mon application permet d'écrire du texte et de dessiner des lignes, des rectangles et des ellipses. Il est également possible de déplacer, agrandir, réduire, sélectionner, couper, copier et coller un ou plusieurs de ces objets. Il est également possible de zoomer et de déplacer le canevas à l'aide de divers raccourcis. En bref, mon application utilise pleinement Fabric.js.

Je n'ai pas pu trouver beaucoup d'informations sur la façon d'utiliser Fabric.js avec React, donc mon souci est que 1. est-ce possible sans modifications majeures, et 2. est-ce logique ou dois-je utiliser à la place une autre bibliothèque de canevas étendue qui a un meilleur support pour React?

Le seul exemple de React + Fabric.js que j'ai pu trouver était react-komik , qui est cependant beaucoup plus simple que mon application. Mes principales préoccupations sont le traitement des événements et la manipulation DOM de Fabric.js, et leur effet sur React.

Il semble qu'il existe également une bibliothèque de canevas pour React, appelée react-canvas , mais il semble manquer de nombreuses fonctionnalités par rapport à Fabric.js.

Que dois-je prendre en compte (concernant la manipulation DOM, le traitement des événements, etc.) lors de l'utilisation de Fabric.js dans une application React?

40
Kitanotori

Nous avons eu ce même problème dans notre application sur la façon d'utiliser Fabric.js à l'intérieur de React. Ma recommandation est de traiter le tissu comme un composant non contrôlé. Ayez une instance de fabric à laquelle toute votre application peut parler et passer des appels de fabric, puis lorsque quelque chose change, utilisez l'appel .toObject() pour mettre tout l'état de fabric dans votre magasin Redux. Votre application React peut alors lire l'état du fabric depuis votre état Redux global comme vous le feriez dans n'importe quelle application React.

Je ne peux pas obtenir un exemple fonctionnant dans l'éditeur de code StackOverflow mais voici un exemple JSFiddle qui implémente le modèle que je recommande.

36
StefanHayden

J'ai utilisé Fabric pour un projet de preuve de concept et l'idée générale est la même que pour, disons, D3. Gardez à l'esprit que Fabric opère sur les éléments DOM, tandis que React rend les données dans DOM, et généralement ce dernier est différé. Il y a deux choses qui vous aideront à vous assurer que votre code fonctionne:

Attendez que le composant soit monté

Pour ce faire, placez votre instanciation Fabric dans 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>
    )
  }
}

Placer le constructeur Fabric dans componentDidMount garantit qu'il n'échouera pas car au moment où cette méthode est exécutée, le DOM est prêt. (mais les accessoires ne le sont parfois pas, juste au cas où si vous utilisez Redux)

Utilisez les références pour calculer la largeur et la hauteur réelles

Refs sont des références à des éléments DOM réels. Vous pouvez faire avec les références ce que vous pouvez faire avec les éléments DOM à l'aide de l'API DOM: sélectionner les enfants, trouver le parent, attribuer les propriétés de style, calculer innerHeight et innerWidth. Ce dernier est précisément ce dont vous avez besoin:

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

  // do some stuff with it
}

N'oubliez pas de définir la propriété refs de this. Pour ce faire, vous aurez besoin d'un constructeur. Le tout ressemblerait à

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>
    )
  }
}

Mélanger le tissu avec l'état des composants ou les accessoires

Vous pouvez faire réagir votre instance Fabric aux accessoires de composant ou aux mises à jour d'état. Pour le faire fonctionner, mettez simplement à jour votre instance Fabric (que vous pouvez voir, comme vous pouvez le voir, faire partie des propriétés du composant) sur componentDidUpdate. Se baser simplement sur les appels de fonction render ne sera pas vraiment utile car aucun des éléments qui sont rendus ne changera jamais sur de nouveaux accessoires ou un nouvel état. Quelque chose comme ça:

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>
    )
  }
}

Remplacez simplement le rendu d'image par le code dont vous avez besoin et cela dépend du nouvel état du composant ou des accessoires. N'oubliez pas de nettoyer la toile avant de créer de nouveaux objets dessus!

17
Fleischpflanzerl

Il y a aussi le package react-fabricjs qui vous permet d'utiliser des objets fabric comme composants react. En fait, la réponse de Rishat inclut ce package, mais je ne comprends pas comment il est censé fonctionner car il n'y a pas d'objet "fabric" dans react-fabricjs (il voulait probablement dire "fabric-webpack" package). Un exemple pour un simple composant "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;

Même si vous ne souhaitez pas utiliser ce package, l'exploration de son code peut vous aider à comprendre comment implémenter Fabric.js dans React par vous-même.

2
Radomír Laučík

J'ai créé react-shape-editor pour un cas d'utilisation similaire, dans lequel Fabric.js avait toutes les fonctionnalités dont j'avais besoin, mais il était difficile de rester synchronisé avec mon code React/Redux. Il n'a pas autant de fonctionnalités que Fabric, mais il peut être un bon substitut à certains cas d'utilisation.

0
Chris