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

Kit*_*ori 42 fabricjs reactjs 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操作,事件处理等)?

Ste*_*den 42

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

我无法在StackOverflow代码编辑器中使用示例,但这是一个JSFiddle示例,它实现了我推荐的模式.


ris*_*hat 18

我已经将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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

放置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
}
Run Code Online (Sandbox Code Playgroud)

别忘了定义refs属性this.要做到这一点,你需要一个构造函数.整个事情看起来像

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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

混合织物与组件状态或道具

您可以使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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

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


jan*_*mon 8

使用 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;
            }
        }
    }, []);
};
Run Code Online (Sandbox Code Playgroud)

用法

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


Win*_*lin 7

我遵循StefanHayden的回答,这是测试代码。

创建 Fabric 画布对象

创建一个自定义钩子以返回fabricRef(Callback Refs):

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;
};
Run Code Online (Sandbox Code Playgroud)

注意:

最后,创建一个画布并传递引用:

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

但是我们不能在其他地方使用fabricCanvas,我将在下一章解释。

代码笔

按上下文共享 Fabric Canvas 对象

通过存储Refs 可变值的Context,我们可以在任何地方使用 Fabric 画布对象。

首先,我们定义上下文,它以 ref 作为值:

const FabricContext = React.createContext();

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

然后,我们可以在自定义钩子中使用它。我们现在不创建引用,而是在上下文中使用引用:

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} />;
}
Run Code Online (Sandbox Code Playgroud)

我们还可以在任何地方使用它,只要上下文可用:

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>;
}
Run Code Online (Sandbox Code Playgroud)

在App的整个生命周期中,现在可以在任何地方使用fabric canvas对象。

代码笔

通过 props 共享 Fabric Canvas 对象

如果不想通过 Context 共享,就像全局变量一样,我们也可以通过 props 与父级共享。

代码笔

通过forwardRef共享Fabric Canvas对象

如果不想通过Context共享,就像全局变量一样,我们也可以通过forwardRef与父级共享。

代码笔