如何将Fabric.js与React一起使用?

时间:2016-06-01 09:43:37

标签: reactjs fabricjs react-canvas

我有一个应用程序通过Fabric.js使用大量HTML5画布。该应用程序是在Angular 1.x之上编写的,我计划将其迁移到React。我的应用程序允许编写文本和绘制线条,矩形和椭圆。还可以移动,放大,缩小,选择,剪切,复制和粘贴一个或多个这样的对象。也可以使用各种快捷方式缩放和平移画布。简而言之,我的应用程序充分利用了Fabric.js。

我无法找到有关如何将Fabric.js与React一起使用的大量信息,所以我关注的是1.是否可以在没有重大修改的情况下进行,2。它是否有意义,或者我应该使用一些其他广泛的画布库对React有更好的支持吗?

我能找到的唯一React + Fabric.js示例是react-komik,但它比我的应用程序简单得多。我主要关心的是Fabric.js的事件处理和DOM操作,以及它们对React的影响。

似乎还有一个用于React的画布库,名为react-canvas,但与Fabric.js相比,它似乎缺少很多功能。

在React应用程序中使用Fabric.js时,我需要考虑什么(关于DOM操作,事件处理等)?

7 个答案:

答案 0 :(得分:34)

我们的应用程序中有关于如何在react内部使用Fabric.js的问题。我的建议是将面料作为一种不受控制的成分。具有整个应用程序可以与之通信的结构实例并进行结构调用,然后在进行任何更改时使用.toObject()调用将整个结构状态放入Redux存储中。然后您的React应用程序可以像在任何正常的React应用程序中那样从您的全局Redux状态读取结构状态。

我无法在StackOverflow代码编辑器中找到一个示例,但here is a JSFiddle example实现了我推荐的模式。

答案 1 :(得分:16)

我已经将Fabric用于概念验证项目,其总体思路与D3相同。请记住,Fabric对DOM元素进行操作,而React将数据呈现为DOM,通常后者是延迟的。有两件事可以帮助您确保代码有效:

等到组件安装

为此,请将Fabric实例化放入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已准备就绪。 (但道具有时候并不是,以防万一你使用Redux)

使用refs计算实际宽度和高度

Refs是对实际DOM元素的引用。您可以使用DOM API对使用DOM元素执行的操作进行操作:选择子项,查找父项,指定样式属性,计算innerHeightinnerWidth。后者正是您所需要的:

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

  // do some stuff with it
}

不要忘记定义refs的{​​{1}}属性。要做到这一点,你需要一个构造函数。整个事情看起来像

this

使用组件状态或道具

混合织物

您可以使Fabric实例对任何组件道具或状态更新做出反应。要使其工作,只需更新您的Fabric实例(您可以看到,您可以将其存储为组件自身属性的一部分)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> ) } } 。简单地依靠componentDidUpdate函数调用确实没有用,因为渲染的元素都不会在新的道具或新状态上发生变化。像这样:

render

只需用您需要的代码替换图像渲染,这取决于新的组件状态或道具。在渲染新对象之前,不要忘记清理画布!

答案 2 :(得分:2)

还有react-fabricjs包允许您将结构对象用作反应组件。实际上,Rishat的答案包括这个包,但我不明白它应该如何工作,因为react-fabricjs中没有'fabric'对象(他可能意味着'fabric-webpack'包)。 简单的“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;

即使您不想使用此软件包,探索它的代码也可以帮助您了解如何在React中实现Fabric.js。

答案 3 :(得分:2)

使用react 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>
}

答案 4 :(得分:0)

我为类似的用例创建了react-shape-editor,Fabric.js具有我需要的所有功能,但是要与我的React / Redux代码保持同步很痛苦。它的功能几乎不如Fabric,但可以很好地替代某些用例。

答案 5 :(得分:0)

我在这里看不到使用React的功能组件的任何答案,并且我无法从@jantimon上钩。这是我的方法。我让画布本身从页面布局中获取尺寸,然后在(0,0)处有一个“ backgroundObject”作为用户的可绘制区域。

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;

答案 6 :(得分:-1)

哇!

如果您来这里期望获得有关Microsoft Fabric的答案,那么您来错了地方。您应该在寻找

(删除原话,略显尴尬的原始答案。)