有什麼辦法可以將Fabric.js和Redux一起使用嗎? Fabric.js狀態應該用作商店的一部分,但它不是不可變的,並且可以通過用戶畫布交互來改變自己。任何想法?謝謝。如何交朋友Fabric.js和Redux?
回答
我找到了一些解決方案。我試圖描述,但對於英語感到抱歉。因爲Fabric.js中不存在任何不可變性,所以使用redux很難實現狀態管理。據我瞭解,默認的解決方案是使用fabric.loadFromJson函數來推送新狀態和序列化以進行下拉和存儲以進行下一個操作,例如操作歷史記錄。但在這種情況下,如果您想使用圖像,JSON解析將會成爲瓶頸,因爲它們將存儲在Base64數據中。
這種方式有點怪異,但它適用於我。我正在替換fabric.js(fabric._objects)的內部對象陣列,並且每次在畫布上發生某些事情時調用渲染,例如,通過鼠標移動物體。
首先,我的狀態現在通過Immutable.js是不可變的,即我必須在我的reducer中返回不可變的List。但是這些列表中的元素並不是不可變的,它只是爲了渲染而存儲的fabric.js對象。我的狀態由對象列表,選擇列表和多個表示例如視口狀態(縮放,平移)的助手對象組成。對象狀態列表鍵用作動作中對象的ID。我的根場景減速器有結構。
const sceneReducer = composeReducers(
whetherRecordCurrentState,
combineReducers({
project: undoable(
composeReducers(
projectActions,
combineReducers({
objects,
params,
counters
}),
),
{
limit: historyLimit,
filter: combineFilters(
recordFilter,
excludeAction([
'CREATE_SELECTION',
'CLEAR_SELECTION',
'SET_WORKSPACE_NAME',
'SET_WORKSPACE_ID',
'SET_WORKSPACE_TYPE',
'SET_TAGS',
]),
)
}
),
selection,
meta,
viewport,
recording
}),
selectJustCreatedObject
);
它實現了任何fabric.js的可能性,包括異步函數,如應用過濾器。另外我使用了redux-undoable包,它允許實現無限制的撤銷/重做歷史記錄。它也允許實現不存儲的操作,例如通過滑塊改變不透明度(所有中間狀態將不被存儲)。由於我使用不變性,我可以只用一個更改的對象來推送新的歷史狀態以節省內存。還有就是我的狀態
https://i.gyazo.com/fcef421e9ccfa965946a6e5930e42edf.png
見它的工作原理:在fabric.js我處理與新對象狀態的事件。然後,我將該狀態的動作分派爲有效載荷。在我的行動中,我可以創建新的結構對象或傳遞更新的對象。所有異步操作(過濾,更改圖像源)在動作中執行並傳遞給reducer準備好的新對象。在reducer中,可以訪問我的fabric.js對象工廠,該工廠通過一個區別創建對象的深層副本。我補丁fabric.js(猴子補丁,但你可以使用原型擴展),它不會序列化圖像base64了。我通過覆蓋方法Object.toDatalessObject()來實現它,它返回沒有圖像數據的相同json。而是通過手動設置Image._element來存儲鏈接到HTMLElement對象的源數據 - uri圖像數據。即改變圖像座標後,新的圖像對象將具有相同的_element。它可以節省內存並加速應用程序。
畢竟,我的fabric.js容器是React組件。它與redux連接,並在提交更改後調用componentWillRecievProps方法。在方法中,我捕獲新的狀態,與我的工廠創建副本(是的,有雙重複制,它應該優化,但它適用於我),並將其傳遞到fabric._objects,然後我調用渲染。 我希望它有幫助。
我已經從我的React-Redux和Fabric.js的實現中提取了一個小例子。
它通過簡單地通過fabric.toObject()獲取整個結構對象,將其保存到狀態並通過fabric.loadFromJSON()來撤銷。您可以通過使用Redux DevTools並在州內旅行玩耍。
對於任何情況下,也有提供的jsfiddle: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 (
\t \t \t <div>
\t \t \t \t <Provider store={store}>
\t \t \t \t \t <FabricCanvasReduxed/>
\t \t \t \t </Provider>
\t \t \t </div>
);
}
});
// 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() {
\t \t \t // 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,
});
\t \t \t // initial call to load objects in store and render canvas
this.refs.canvasContainer.loadAndRender();
\t \t \t
fabricCanvas.on('mouse:up',() => {
store.dispatch({
type: 'OBJECTS_CANVAS_CHANGE',
payload: {
\t \t \t \t \t \t // send complete fabric canvas object to store
canvasObject: fabricCanvas.toObject(),
\t \t \t \t \t \t // also keep lastly active (selected) object
selectedObject: fabricCanvas.getObjects().indexOf(fabricCanvas.getActiveObject())
}
});
this.refs.canvasContainer.loadAndRender();
});
},
render: function() {
return (
\t \t \t \t <div>
\t \t \t \t \t {/* 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) */}
\t <CanvasContainer ref="canvasContainer" canvasObjectState={this.props.objects} fabricInstance={fabricCanvas}/>
\t \t \t \t </div>
)
}
});
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;
\t \t fabricCanvas.loadFromJSON(this.props.canvasObjectState.canvasObject);
\t \t fabricCanvas.renderAll();
\t \t // if there is any previously active object, we need to re-set it after rendering canvas
\t \t var selectedObject = this.props.canvasObjectState.selectedObject;
\t \t if (selectedObject > -1) {
\t \t \t fabricCanvas.setActiveObject(fabricCanvas.getObjects()[this.props.canvasObjectState.selectedObject]);
\t \t }
},
render: function() {
this.loadAndRender();
return (
\t \t \t <canvas ref="objectsCanvas">
</canvas>
);
}
});
var App = React.createClass({
render: function() {
return (
\t \t \t \t \t <div>
\t <MyProvider/>
</div>
\t \t \t \t);
\t \t \t }
\t });
\t
ReactDOM.render(<App/>, document.getElementById('container'));
<!--
\t Please use Redux DevTools for Chrome or Firefox to see the store changes and time traveling
\t https://github.com/zalmoxisus/redux-devtools-extension
\t Inspired by https://jsfiddle.net/STHayden/2pncoLb5/
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/4.4.5/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.js"></script>
<div id="container">
</div>
- 1. 交友朋友的隨機朋友
- 2. 如何讓我的朋友和朋友的朋友?
- 3. 與考拉交朋友朋友名單
- 4. Facebook API交友朋友
- 5. Graph-Traversal:我如何使用Gremlin查詢「朋友和朋友的朋友」
- 6. 如何獲得朋友的朋友的朋友...
- 7. SQL獲取朋友和朋友的朋友
- 8. 圖 - 顯示朋友(和第一學位朋友)的朋友
- 9. Facebook上的朋友和朋友列表
- 10. SMJobBless和朋友
- 11. SQL表爲朋友,共同朋友,朋友朋友等
- 12. 交朋友facebook牆帖
- 13. Facebook朋友的朋友
- 14. 如何獲取用戶朋友中的每一個朋友的朋友數?
- 15. 模板和朋友
- 16. 在php朋友系統中,如何從朋友桌上打印朋友的朋友
- 17. 如何獲取所有朋友的朋友,這是朋友在facebook的朋友列表
- 18. 從朋友和朋友的朋友那裏得到僱主的人數 - facebook app
- 19. 返回使用我的朋友和朋友的朋友的Neo4j的Cypher
- 20. 朋友課如何互動
- 21. 如何算的朋友
- 22. 如何獲得朋友
- 23. PostgreSQL的:朋友的朋友,但我的那些朋友們
- 24. php朋友系統,顯示我朋友的朋友
- 25. 如何獲得有相同興趣的朋友的朋友?
- 26. 如何讓朋友的朋友的名單C#.NET
- 27. 如何從朋友的表中查詢朋友的名字
- 28. 如何使用朋友功能或朋友類?
- 29. 如何檢索朋友列表中的Facebook朋友?
- 30. 如何獲得朋友列表與共同朋友的數量?
你有沒有發現任何解決這個? –
@ChetanSachdev我試圖解釋我的答案 – equicolor