paperjs / paper.js

The Swiss Army Knife of Vector Graphics Scripting – Scriptographer ported to JavaScript and the browser, using HTML5 Canvas. Created by @lehni & @puckey
http://paperjs.org
Other
14.33k stars 1.22k forks source link

Redux support #1178

Open Chaoste opened 7 years ago

Chaoste commented 7 years ago

I'm working on a redux project which supports adding canvas elements using paper.js . I'm new to redux and to get some experiences I've looked at some tutorials and examples. But I'm wondering how I could use paper.js without going against the design rules of redux. In my opinion there are two options:

  1. Maintain a list of JS objects for all canvas elements with the required informations to create a path object. On each action (like adding & removing elements) the canvas get's cleared and I generate path objects with the informations contained by my global redux store (the state). Pro: Stay as close as possible to the designing rules of redux. Contra: Redundancy: The canvas elements are hold in two different locations and can get incoherent (e.g. by moving or selecting elements); Performance: Sometimes the application won't act smoothly (all the more if there are many elements to be drawn).
  2. Keep a reference to the Paper elements in the redux state. I already found out where they are stored ("Paper.project._children[0]._children"). Pro: Prevent redundancy and performance problems, No redux actions and reducers needed to handle user interactions with the canvas (like moving and selecting elements). Contra: I completely neglect the three principles of redux ("Single source of truth", "State is read-only", "Changes are made with pure functions"). This could end in some weird bugs and also I think undo and redo action will be hard to implement.

Maybe there is a third option you can suggest but I want build a clean app which sticks as close as possible to the rules. I'm sorry for writing this into a Paper.js issue but in some way this is an issue other people will face if they are building a redux app using Paper.js. If you don't agree you can close this issue and I'll ask on Stackoverflow. 😅

Greetings, Thomas

Chaoste commented 7 years ago

For now I decided to go with the first solution. I seems to me to be the cleanest way. Saving elements two times won't probably make a huge difference because the stored information is very small.

If someone has the same problem where to actually create the Paper objects:

componentWillReceiveProps(nextProps) {
  if (nextProps.shouldRender) {
    Paper.project.clear()
    this.addElements(nextProps.elements)
    this.props.resolveRenderingOrder()
  }
},

shouldComponentUpdate() {
  // Call the render method only once in the beginning (this is just for performance)
  return false
},

The method "componentWillReceiveProps" is the best way to find out if of of my elements changed. If so I clear the entire project and rebuild each component with another method.

SebastianStehle commented 7 years ago

I also have a redux application, but I use the following approach:

Each item in my model has an id and the renderer holds map, with this structure

rendererElements = {
  "1": {
      item,
      paperJsElement
   }
}

For each change I do the following steps:

  1. Remove all items from the layer
  2. Iterate over all items from the model/store
    • If the item is different to the item in rendererElements recreate it
    • If there is no item with the id in rendererElements create it.
  3. Add all items to the layer in the right order.

=> This means that I only recreate changed elements, which is very fast. For my adorners, I use another canvas on top of it, therefore I have a much less drawing calls.

See it in action: http://athene.weblaunches.net/

lehni commented 7 years ago

@SebastianStehle wow, this is impressive.

I have never used Redux myself, but am curious to find out if I could somehow help making this a part of the library. Where would I have to start looking into creating bindings?

SebastianStehle commented 7 years ago

Hi Lehni,

the project is on hold, so I made the source code public: https://github.com/SebastianStehle/Athene2/blob/master/src/Athene/app/wireframes/renderer/editor-view.component.ts

There is a renderDiagram() method which does all the work. The good thing about redux is: It is so easy and I think you made a wonderful job with Paper.js and it is not that much work to get the binding working. I would not do anything at all. ;)

I struggled a little bit with rounding issues and to get borders that are within my rects and do not overlap them. Therefore I made some abstraction over paper.js and it almost works https://github.com/SebastianStehle/Athene2/blob/master/src/Athene/app/wireframes/shapes/utils/paper-renderer.ts ... Would be nice to have a solution for that.

Chaoste commented 7 years ago

@lehni Are you still looking for a redux binding for paper.js? Maybe I can support you in a way even though I didn't look into the source code of Paper.js until now.

lehni commented 7 years ago

@Chaoste I'm not actively looking, but if somebody wanted to provide and maintain such a library, I would be happy to link to it. I am not using React myself...

HriBB commented 7 years ago

Hey guys, just wanted to let you know that I'm working on Paper.js bindings for React Fiber react-paper-bindings. Would love to get some feedback, especially from @lehni on the proper way to handle draw, pan, zoom and animations in React world.

I am using a custom react and react-dom version 16.0.0-alpha.13 which exposes ReactFiberReconciler through react-dom. It's still very early in development and I am still figuring things out, but take a look at the following files:

If we somehow manage to keep the state of canvas in Redux, that would be awesome. But I think it might be an overhead, so maybe just keep an object in state? Will post some ideas and question later.

SebastianStehle commented 7 years ago

What are your use cases you would like to cover? I am not sure, if you can manage a complex application in this way.

HriBB commented 7 years ago

@SebastianStehle I am also not sure ;)

I am trying to create an image editor with tools like pan, zoom, draw, undo, redo and save. This is basically an experiment with React Fiber. It's heavily based on https://github.com/reactjs/react-art.

I need to recreate my demo in plain javascript, to see the performance impact of React Fiber.

BTW, why do you think it wont work?

HriBB commented 7 years ago

Here's the demo http://hribb.github.io/react-paper-bindings/

HriBB commented 7 years ago

I have made some progress with the React Fiber bindings. Demo now supports history with undo/redo functionality. Current implementation can easily be integrated into a Redux store.

I was able to implement history by using getPathData(). On Tool mousedown I create a temporary Path, then on ˙mouseup˙ I add getPathData() to the collection of paths that are rendered by React, and remove the temporary Path. This approach looks really promising for my use case. I still need to figure out how to scale paths for different view sizes on initial render.

I also made a simple performance test by translating the view with 150 circles using React and pure javascript. Here are the results:

React react

Pure javascript paperjs

As you can see, there's a lot more scripting happening with React Fiber compared to pure javascript. Will look into optimizations soon.

alsymiya commented 4 years ago

@HriBB Hi, I downloaded your demo but I can't start it in my machine (win10). It always fail to compile. I just did npm install npm test It goes wrong:

    at HTMLCanvasElementImpl.getContext (\react-paper-bindings\node_modules\jest-environment-jsdom\node_modules\jsdom\lib\jsdom\living\nodes\HTMLCanvasElement-impl.js:42:5)
    at HTMLCanvasElement.getContext (\react-paper-bindings\node_modules\jest-environment-jsdom\node_modules\jsdom\lib\jsdom\living\generated\HTMLCanvasElement.js:50:45)
    at Object.getCanvas (\react-paper-bindings\node_modules\paper\dist\paper-core.js:13645:20)
    at Object.getContext (\react-paper-bindings\node_modules\paper\dist\paper-core.js:13662:21)
    at new <anonymous> (\react-paper-bindings\node_modules\paper\dist\paper-core.js:13861:27)
    at Object.<anonymous> (\react-paper-bindings\node_modules\paper\dist\paper-core.js:13675:17)
    at Object.<anonymous> (\react-paper-bindings\node_modules\paper\dist\paper-core.js:14977:3)
    at Runtime._execModule (\react-paper-bindings\node_modules\jest-runtime\build\index.js:694:13)
    at Runtime.requireModule (\react-paper-bindings\node_modules\jest-runtime\build\index.js:376:14) undefined

Also, @lehni , if you are willing to help. Can someone provide me a simpler example on how to implement paper.js with redux? I am new to many packages and the example you provided is not running in my local machine, so I can't try and modify it.