I’m trying to make a small SVG graphics editor for learning purposes.
When the user is moving elements in the editor, I want to react to mousemove events that occur in the SVG field.
To transform mouse coordinates to SVG coordinates, I need to use JavaScript code to transform fields of the mousemove event.
I solved this by creating a custom event as described in here. In a nutshell, I listen for mousemove events and dispatch a custom event with the transformed coordinates.
A demo is here, based on the code of @benthepoet given in above link that has been very helpful.
The problem is that currently my custom event is always fired whenever the mouse is moved, and given that the SVG coordinate transformation performed in this event might be costly, I would like this calculation to be performed only when the application actually needs to react to mouse move events, i.e. when users move a graphic in the SVG editor.
So how can I make the calculation only happen when I need it?
The nicest thing would be if the custom event would not actually perform the calculation, but could return a lazy value that the Elm part could evaluate only if needed.
Ports might be a solution, but given that I would need to pass data to JS and get data back from it, this option looks quite cumbersome.
Thanks @malaire , that’s good to know. But just in general, if the calculation performed would be more costly, what would be a good way to perform it only if necessary?
I just created an example of how to do the client-to-SVG coordinate transformation in Elm without custom event.
This works by sending the ScreenCtm values needed for calculation to Elm with ports, and then using normal mousemove event. The ScreenCtm will change whenever position or size of svg-element changes. For now I implemented just a simple version where new ScreenCtm is requested whenever window is resized. (I could just listen for onResize in JavaScript, but I decided to keep everything Elm-driven, so when Elm detects onResize, it requests new ScreenCtm from JavaScript.)
As for your use case, when just using normal mousemove, you could easily enable/disable that event listener in Elm if needed, or decide to not calculate coordinate transformation on every event. But as you see in code it’s really quite small calculation.
btw, here’s the math needed for coordinate transformation (taken from here):
type alias ScreenCtm =
{ a : Float
, b : Float
, c : Float
, d : Float
, e : Float
, f : Float
}
type alias Point =
{ x : Float
, y : Float
}
matrixTransform : Point -> ScreenCtm -> Point
matrixTransform p c =
{ x = c.a * p.x + c.c * p.y + c.e
, y = c.b * p.x + c.d * p.y + c.f
}
Thanks a lot @malaire for the effort you took to create this very nice example!
With your help, I was able to integrate the SVG coordinate transformation into my program. (For other people reading, the code in use can be found here.)
Having some helper function that would extract the SVG coordinates from Svg.Events.onClick, Svg.Events.on "mousemove" and so on would be definitely nice, but I guess that putting these directly into Svg.Events would look a bit strange, given that Svg.Events is quite minimalistic and does not even define helper functions to obtain the screen coordinates.
I think that what might help in the short run would be just putting a link in the documentation for Svg.Events.onClick to some page that explains how to treat the message so that e.g. the SVG coordinates can be extracted with the help of ports. If this would be appreciated, I could write a little tutorial based on the example of @malaire how to do this.
One thing to note about my solution with ports is that if location/size of svg element can change at all, e.g. if there are other elements which might change, and then cause svg element to move, then new ScreenCtm must be requested every time svg element moves or changes size. (Or if viewport of svg element is changed.)
Also if that happens while moving mouse in svg element, then it is possible to have wrong ScreenCtm for some mousemove events.
I’m thinking that best option might be to somehow have a “subscription” which sends new ScreenCtm when needed. I havn’t figured out yet how to implement this.