Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using MorhpSVG plugin without animation #118

Open
lordanubi opened this issue Feb 17, 2023 · 6 comments
Open

Using MorhpSVG plugin without animation #118

lordanubi opened this issue Feb 17, 2023 · 6 comments

Comments

@lordanubi
Copy link

lordanubi commented Feb 17, 2023

I'm working on a React Component animated Transform that uses SVGPathCommander under the hood:

const AnimatedTransform = ({children,...transfProps }) => {
  if (window.isBrowser) {
    return children.map(path => {
      const { transformeD, originalD, d, ...pathProps } = path.props;
      const after = transformPath(d, {...transfProps})
      return <animated.path {...pathProps} {...getMorph(d, after)} />
    })
  }
  return children
}

transformPath is a wrapper for SVGPathCommander basic methods:

export default function transformPath(pathString, transformProps) {
    const pathCommander = new SVGPathCommander(pathString)
    const { flipX, flipY, rotate, scale, scaleX, scaleY, x, y, origin, optimize ,reverse} = transformProps;
    if (flipX) pathCommander.transform({rotate: [0, 180, 0], origin: origin });
    if (flipY) pathCommander.transform({ rotate: [180, 0, 0], origin: origin });
    if (rotate) pathCommander.transform({ rotate, origin: origin });
    if (scale || scaleX || scaleY) pathCommander.transform({ origin: origin, scale: [scaleX || scale || 1, scaleY || scale || 1] });
    if (x || y) pathCommander.transform({ translate: [x, y] });
 
    if (optimize) pathCommander.optimize().optimize()
    return pathCommander.toString()
 }
 export default function getMorph(from, to) {
    const springProps = useSpring({ from: { d: from }, to: { d: to } ,config: { mass: 1, tension: 280, friction: 120 }})
    return {...springProps, originalD: from, transformeD: to}
  }

while getMorph is a function that uses React Spring to get the interpolated d attribute that gets the path to morph from what it was before the transform to what it was after.
It's working for simple transforms, but it won't work for complex paths that gets transformed too much (ofc it won't work with path optimisation as well). I was wondering if there's any way to still use React Spring for physics animations and SVGPathCommnader to make the transforms but have some sort of utility function from KUTE.js that prepare the before and after path string so that it animates correctly from one to the other.

And by the way thanks again to all the amazing work you did in the SVG field. It's really appreciated!

@thednp
Copy link
Owner

thednp commented Feb 18, 2023

When you do KUTE.fromTo(target, { path: startPath }, { path: finishPath }) this creates a tween object with the computed values you need.

For that you can try and import any of the two morph components and try to "emulate" what KUTE.js does to create tween object SVGMorph.Util.getInterpolationPoints(path1, path2, precision) or SVGCubicMorph.Util.equalizeSegments(path1, path2). In the future I'm considering a merger of the two components but for now this is what we've got.

@lordanubi
Copy link
Author

equalizeSegments was exactly what i was looking for since i want the interpolation to be done by React Spring. I was hoping it was more useful for my case though. It still doesn't work if i morph from one path to the same path transformed by SVGPathCommander with optimization on. But what i really need it for that would be morphing from a path to another one composed of 2 times the first path half sized. Basically I would like to morph from one logo to 2 or 4 smaller logos but that's certainly some complicated graphics behaviour that I still need to study on.
thanks for the help!

@thednp
Copy link
Owner

thednp commented Feb 20, 2023

You can't optimize the result of equalizeSegments, you should only round them to 2-3 decimals. Optimization might remove segments and morph animation requires to have 2 paths of same amount of segments.

@CollectionOfAtoms
Copy link

Hoping to hijack this thread for what I believe is a similar use case.

I'm pretty new to SVG manipulation, but have managed to make an in-browser visualizer project: https://collectionofatoms.github.io/sprialator/
https://github.com/CollectionOfAtoms/sprialator
(press s to show the shape morph)

I have implemented a smooth morphing between basic shapes using D3s morphing function, but am highly limited, as it doesn't look good morphing between shapes of different kinds of paths like circle-to-triangle, or shapes with different numbers of points.

To achieve what I already have, I precompute all morphs between all possible shapes and then pick out the correct in-between shape during my morphing window. This all seems very similar to what Tween is doing. But I don't think I can use the Tween's animation functions play well with what I'm doing here. Or at the very least I don't understand them well :P

If I can find a way to precompute morphs using KUTE, I think that I could use more complex shapes which would make it a lot more interesting to look at :)

Is there a way to leverage KUTE to do this? How would I access the 'd' property of the intermediate SVG paths?

Thank you for your time and attention. KUTE is an amazing library and morphing is a haaaaaaard problem! Very grateful you put this out there.

@thednp
Copy link
Owner

thednp commented Mar 30, 2024

If I'm understanding this right, you;re looking for intermediate values? Check this out
https://thednp.github.io/kute.js/progress.html

@CollectionOfAtoms
Copy link

CollectionOfAtoms commented Apr 18, 2024

Thanks for sending me that. I was looking at it, but it seemed like maybe it was still sending its own requestAnimationFrame() calls within it when the tween is playing.

I ended up doing a pretty hacky workaround. I initialize tweens for every path that I use and then once right on page load, I manually update the tween with a value and then read the path off of the element like this.

`function getMorphStepsFromTween(tween, numMorphSteps, tweenElementId, duration=1000){
var stepNum = 1
const morphSteps = []

while (stepNum <= numMorphSteps){
    const progress = stepNum/(numMorphSteps-150)  // Why -150?  because shapes don't completely transform if I don't.
    tween.update(progress * duration)
    var currentPathData = document.getElementById(`${tweenElementId}-start`).getAttribute('d');
    morphSteps.push(currentPathData)

    stepNum++
}

return morphSteps

}`

It seems like that value that gets passed in needs to go well beyond the duration set in the fromTo() call, but also pretty sure this isn't intended to be used this way. I think I could probably do the same with a ProgressBar but honestly I'm pretty new to this stuff and don't understand what's going on.

Thank you for your help.

Do you have any advice for morphing very unlike paths?
I'm finding that some tweens kind of look like the image being skewed until they are just a line, and then suddenly changing to the other shape. I'm trying to get the smoothest morphs possible. Would be happy to pay you for an hour or two of your time to look at my use case which is pretty atypical.

Loving using KUTE though it's really improved my visualizer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants