How to make friends Fabric.js and Redux?

前端 未结 2 1469
遥遥无期
遥遥无期 2020-12-28 11:12

Is there any approach to use Fabric.js and Redux together? Fabric.js state should be used as part of store, but it isn\'t immutable and can mutate itself by user canvas inte

2条回答
  •  青春惊慌失措
    2020-12-28 11:31

    I have extracted small example from my implementation of React-Redux and Fabric.js.

    It works by simply getting whole fabric object by fabric.toObject(), saving it into state and revoking by fabric.loadFromJSON(). You can play around by using Redux DevTools and traveling through the state.

    For any case, there is also jsfiddle available: https://jsfiddle.net/radomeer/74t5y1r0/

    // don't be scared, just some initial objects to play with (fabric's serialized JSON)
    const initialState = {
       canvasObject: {
          "objects": [{
             "type": "circle",
             "originX": "center",
             "originY": "center",
             "left": 50,
             "top": 50,
             "width": 100,
             "height": 100,
             "fill": "#FF00FF",
             "stroke": null,
             "strokeWidth": 1,
             "strokeDashArray": null,
             "strokeLineCap": "butt",
             "strokeLineJoin": "miter",
             "strokeMiterLimit": 10,
             "scaleX": 1,
             "scaleY": 1,
             "angle": 0,
             "flipX": false,
             "flipY": false,
             "opacity": 1,
             "shadow": null,
             "visible": true,
             "clipTo": null,
             "backgroundColor": "",
             "fillRule": "nonzero",
             "globalCompositeOperation": "source-over",
             "transformMatrix": null,
             "radius": 50,
             "startAngle": 0,
             "endAngle": 6.283185307179586
          }, {
             "type": "rect",
             "originX": "center",
             "originY": "center",
             "left": 126,
             "top": 210,
             "width": 100,
             "height": 100,
             "fill": "#FF0000",
             "stroke": null,
             "strokeWidth": 1,
             "strokeDashArray": null,
             "strokeLineCap": "butt",
             "strokeLineJoin": "miter",
             "strokeMiterLimit": 10,
             "scaleX": 1,
             "scaleY": 1,
             "angle": 0,
             "flipX": false,
             "flipY": false,
             "opacity": 1,
             "shadow": null,
             "visible": true,
             "clipTo": null,
             "backgroundColor": "",
             "fillRule": "nonzero",
             "globalCompositeOperation": "source-over",
             "transformMatrix": null,
             "radius": 50,
             "startAngle": 0,
             "endAngle": 6.283185307179586
          }, {
             "type": "triangle",
             "originX": "center",
             "originY": "center",
             "left": 250,
             "top": 100,
             "width": 100,
             "height": 100,
             "fill": "#00F00F",
             "stroke": null,
             "strokeWidth": 1,
             "strokeDashArray": null,
             "strokeLineCap": "butt",
             "strokeLineJoin": "miter",
             "strokeMiterLimit": 10,
             "scaleX": 1,
             "scaleY": 1,
             "angle": 0,
             "flipX": false,
             "flipY": false,
             "opacity": 1,
             "shadow": null,
             "visible": true,
             "clipTo": null,
             "backgroundColor": "",
             "fillRule": "nonzero",
             "globalCompositeOperation": "source-over",
             "transformMatrix": null,
             "radius": 50,
             "startAngle": 0,
             "endAngle": 6.283185307179586
          }],
          "background": ""
       }
    };
    // Redux part
    const canvasObjectReducer = function(state = initialState, action) {
       switch (action.type) {
          case "OBJECTS_CANVAS_CHANGE":
             return Object.assign({}, state, {
                canvasObject: action.payload.canvasObject,
                selectedObject: action.payload.selectedObject
             });
          default:
             return state
       }
       return state;
    }
    // standard react-redux boilerplate
    const reducers = Redux.combineReducers({
       canvasObjectState: canvasObjectReducer
    });
    const { createStore } = Redux;
    const store = createStore(reducers, window.devToolsExtension && window.devToolsExtension());
    
    const { Provider } = ReactRedux;
    const { Component } = React;
    const MyProvider = React.createClass({
       render: function() {
          return ( 
    			
    ); } }); // Fabric part var fabricCanvas = new fabric.Canvas(); // class which takes care about instantiating fabric and passing state to component with actual canvas const FabricCanvas = React.createClass({ componentDidMount() { // we need to get canvas element by ref to initialize fabric var el = this.refs.canvasContainer.refs.objectsCanvas; fabricCanvas.initialize(el, { height: 400, width: 400, }); // initial call to load objects in store and render canvas this.refs.canvasContainer.loadAndRender(); fabricCanvas.on('mouse:up', () => { store.dispatch({ type: 'OBJECTS_CANVAS_CHANGE', payload: { // send complete fabric canvas object to store canvasObject: fabricCanvas.toObject(), // also keep lastly active (selected) object selectedObject: fabricCanvas.getObjects().indexOf(fabricCanvas.getActiveObject()) } }); this.refs.canvasContainer.loadAndRender(); }); }, render: function() { return (
    {/* send store and fabricInstance viac refs (maybe not the cleanest way, but I was not able to create global instance of fabric due to use of ES6 modules) */}
    ) } }); const mapStateToProps = function(store) { return { objects: store.canvasObjectState }; }; // we can not use export default on jsfiddle so we need react class with mapped state in separate constant const FabricCanvasReduxed = ReactRedux.connect(mapStateToProps)(FabricCanvas); const CanvasContainer = React.createClass({ loadAndRender: function() { var fabricCanvas = this.props.fabricInstance; fabricCanvas.loadFromJSON(this.props.canvasObjectState.canvasObject); fabricCanvas.renderAll(); // if there is any previously active object, we need to re-set it after rendering canvas var selectedObject = this.props.canvasObjectState.selectedObject; if (selectedObject > -1) { fabricCanvas.setActiveObject(fabricCanvas.getObjects()[this.props.canvasObjectState.selectedObject]); } }, render: function() { this.loadAndRender(); return ( ); } }); var App = React.createClass({ render: function() { return (
    ); } }); ReactDOM.render( , document.getElementById('container'));
    
    
    
    
    
    
    

提交回复
热议问题