如何将 Fabric.js 与 React 一起使用?
Posted
技术标签:
【中文标题】如何将 Fabric.js 与 React 一起使用?【英文标题】:How can I use Fabric.js with React? 【发布时间】:2016-09-30 14:35:17 【问题描述】:我有一个通过Fabric.js 使用大量html5 画布的应用程序。该应用程序是在Angular 1.x 之上编写的,我计划将其迁移到React。我的应用程序允许编写文本和绘制线条、矩形和椭圆。也可以移动、放大、缩小、选择、剪切、复制和粘贴一个或多个这样的对象。还可以使用各种快捷方式缩放和平移画布。简而言之,我的应用充分利用了 Fabric.js。
我找不到太多关于如何将 Fabric.js 与 React 一起使用的信息,所以我担心的是 1. 是否可以不进行重大修改,以及 2. 是否有意义或者我应该改用其他一些广泛的对 React 支持更好的 canvas 库?
我能找到的唯一 React+Fabric.js 示例是 react-komik,但它比我的应用程序简单得多。我主要关心的是 Fabric.js 的事件处理和 DOM 操作,以及它们对 React 的影响。
似乎还有一个用于 React 的画布库,名为 react-canvas,但与 Fabric.js 相比,它似乎缺少很多功能。
在 React 应用程序中使用 Fabric.js 时,我必须考虑什么(关于 DOM 操作、事件处理等)?
【问题讨论】:
您可以查看github.com/lavrton/react-konva。我认为移植到 Fabric(而不是 Konva)应该很容易。 这是个有趣的库,但不是我问的。我已经在大范围使用 Fabric.js,所以我更愿意避免切换到其他画布库或过多地篡改 Fabric.js 内部,因为这会使维护变得很麻烦。 react-faux-dom 可以与 d3 和 fabric 等包一起使用。 【参考方案1】:我们在我们的应用程序中遇到了同样的问题,即如何在 react 中使用 Fabric.js。我的建议是将织物视为不受控制的组件。拥有整个应用程序可以与之通信的结构实例并进行结构调用,然后当发生任何变化时,使用.toObject()
调用将整个结构状态放入您的 Redux 存储中。然后,您的 React 应用程序可以像在任何普通 React 应用程序中一样从全局 Redux 状态读取结构状态。
我找不到在 *** 代码编辑器中工作的示例,但 here is a JSFiddle example 实现了我推荐的模式。
【讨论】:
在每次状态更改时调用 toObject 听起来有点矫枉过正。它不会让您的应用程序变得非常缓慢吗? 没有。这是一个非常快的电话。此外,当 anything 最后发生变化时,您也不会调用它。因此,如果您正在拖动一个形状,您只需在鼠标向上时调用toObject
。
现在是否可以在 jsfiddle 或其他任何地方添加您的示例,因为它现在返回 404?谢谢。
感谢您的示例。您能否详细说明在上面的示例中添加 Redux 商店(或任何用于绘图的应用程序)的优点?商店只保存了结构状态的副本,应用程序的其余部分可以从中读取,但所有更改/操作都在 fabricCanvas 上完成。 (我是这方面的新手)【参考方案2】:
我已将 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 已准备就绪。 (但 props 有时不是,以防万一你使用 Redux)
使用 refs 计算实际的宽高
Refs 是对实际 DOM 元素的引用。你可以使用 refs 来做你可以使用 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
不要忘记定义this
的refs
属性。为此,您需要一个构造函数。整个事情看起来像
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
上更新您的 Fabric 实例(如您所见,您可以将其存储为组件自身属性的一部分)。仅仅依靠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>
)
只需用您需要的代码替换图像渲染,这取决于新的组件状态或道具。不要忘记在画布上渲染新对象之前清理它!
【讨论】:
你能帮我解决这个问题吗?我无法在画布中设置背景图片***.com/questions/41581511/… 看来你对 reactjs 和 fabricjs 很有经验。我需要你的帮助。我可以设置背景图像,但无法设置背景颜色。 ***.com/questions/37565041/… 。你能看看这个吗? 我有点困惑这是如何工作的。我看不到react-fabricjs
在任何地方暴露fabric
对象。所以这一行:import fabric from 'react-fabricjs';
为我创建了一个错误。
我认为我的回答已经过时了。如果你用最新版本的 react-fabricjs 解决了这个问题,你介意编辑答案还是写一个新答案?【参考方案3】:
使用 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>
【讨论】:
你不能这样做--- ``` const useFabric = ( opts: width, number , onChange ) => const ref = useRef(null); useEffect(() => const fabricInst = new fabric.Canvas(ref.current); onChange(fabricInst); , [onChange, opts.height, opts.width]);返回参考; ; ``` 如果第一个useEffect
运行的引用是 null
,这将不起作用,因为更改引用将不会再次执行 useEffect
对,但是这样使用的useEffect应该相当于componentDidMount。如果我没记错的话,到那时 ref 保证存在
否,因为您可以延迟渲染 div,例如return someCondition ? <div ref=ref>...</div>: null
- 也显示在官方文档中:reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
我正在尝试使用它,但我不够聪明:当我稍后尝试访问 ref.current(来自上面的 FabricDemo)时,它始终为空。但是画布在页面上显示正常【参考方案4】:
我在这里没有看到使用 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;
【讨论】:
【参考方案5】:还有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
>
<Text
text="Hello World!"
left=300
top=300
fill="#000000"
fontFamily="Arial"
/>
</Canvas>
);
);
export default HelloFabric;
即使你不想使用这个包,探索它的代码可能会帮助你了解如何自己在 React 中实现 Fabric.js。
【讨论】:
另请参阅我对连接 fabric.js 和 redux 的问题的回答,我使用普通的 fabricjs 并将其内容保存到 redux 商店:***.com/questions/37742465/… 看起来 react-fabricjs 项目已经死了(github 链接显示 404)。 评论来自 2016 年,看起来该项目从那时起并没有太多维护,但仍然可以在这里找到:github.com/neverlan/react-fabricjs 不管怎样,在 2020 年,我建议从用户 jantimon。【参考方案6】:我关注了StefanHayden 的回答,这里是测试代码。
创建织物画布对象
创建一个custom hook 以返回一个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;
;
注意:
我们应该使用React.useCallback(fn, [])
到 prevent double calling 的 ref 和 null。如果element为null,则表示componentWillUnmount,所以我们必须在这里dispose fabric。
请使用// eslint-disable-next-line react-hooks/exhaustive-deps
到surpress warnings,用于空数组deps 的useCallback。
fabricCanvas(fabric.Canvas)类似于useFabirc的instance variable,也是Uncontrolled Components,也就是fabric.Canvas对象,可以在你的所有应用中使用。
最后,创建一个画布并传递 ref:
function App()
const fabricRef = useFabric();
return <canvas ref=fabricRef width=640 height=360/>;
但是我们不能在其他地方使用fabricCanvas,我会在下一章解释。
Codepen
按上下文共享 Fabric Canvas 对象
通过存储Refs mutable value的Context,我们可以在任何地方使用织物画布对象。
首先,我们定义上下文,它以一个 ref 为值:
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>;
在 App 的整个生命周期中,它现在可以在任何地方使用织物画布对象。
Codepen
通过道具分享 Fabric Canvas 对象
如果不想通过 Context 共享,比如全局变量,我们也可以通过 props 与 parent 共享。
CodePen
通过 forwardRef 共享 Fabric Canvas 对象
如果不想通过 Context 共享,比如全局变量,我们也可以通过 forwardRef 与 parent 共享。
CodePen
【讨论】:
以上是关于如何将 Fabric.js 与 React 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章
如何将 React Context API 与 useReducer 一起使用以遵循与 Redux 类似的风格 [关闭]
如何将 react-datepicker 与 redux 表单一起使用?
如何在 React.js 中从 fabric.js 渲染 svg 字符串?
如何在fabric js中使用image.centerObject()使对象居中后获取图像坐标