Jump to content
Search Community

Positioning elements along a bezier curve?

green_machine test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

14 hours ago, fcdobbs said:

Is it possible in GSAP to select each of the five coordinates that define the svg path in the createStem function and tween them to new values?

I read that at least 5 times and I'm still fuzzy on what you're asking. Are you saying you want to tween the "d" attribute of the <path> to a new set of values? Sure. But of course if you want all the leaves to move with it, that adds a lot of complexity (you'd probably need to use an onUpdate to reposition stuff on every tick). MorphSVGPlugin makes morphing a <path> very easy. But if you have the exact same number of points and the same draw commands (so the only differences between the start/end values are the numbers), you can just use the attr:{d: "..."} syntax. 

 

Good luck!

Link to comment
Share on other sites

Thanks.

 

First I'd like to learn how to refresh the stem in the example above with new coordinates on repeat , and then I'd like to learn how to animate the stem.

 

My thought was that the most efficient way to build the stem again with new coordinates would be through a callback function that builds the stem from scratch and is executed at the start each time it's played, including the initial run and all subsequent runs.

 

There's something I don't understand about callback functions and repeatRefresh.  When I add a simple timeline through an onStart callback with repeatRefresh set to true, I get an infinite loop. 

 

Also, every time the callback executes the .iteration() logs as 1.

 

What's the best structure for putting a timeline on repeat and building it from scratch every time without getting stuck in an infinite loop?

 

In this pen repeatRefresh is set to "false" initially.

 

Thanks for your help.

 

See the Pen WNgvGXe by fcdobbs (@fcdobbs) on CodePen

 

 

Link to comment
Share on other sites

49 minutes ago, fcdobbs said:

My thought was that the most efficient way to build the stem again with new coordinates would be through a callback function that builds the stem from scratch and is executed at the start each time it's played, including the initial run and all subsequent runs.

I think you might be over-engineering things a bit. A simple recursive function seems much more appropriate than an infinitely repeating timeline that keeps nesting more and more timelines.

 

First let me acknowledge that I think the proper behavior for the onStart is to only get called on the very first iteration and that'll be resolved in the next release. Currently if you've got repeatRefresh, it's calling the onStart each time the playhead goes back to 0 and moves again. 

 

Regardless of that, though, let's walk this through and see how you're creating a fundamental timing flaw logically when you keep adding new stuff into a repeating timeline. Let's say you've got a 1-second long timeline that repeats 3 times. The totalDuration() would be 3. So imagine the global timeline's playhead sweeping over that timeline. When the playhead is 1.5 seconds into that repeating timeline, that would be halfway through the second iteration. 

 

Now let's say when it hits exactly 1 second, it repeats and right at that spot you add a NEW 1-second animation to that timeline, making the timeline 2 seconds long (with repeats, 6 seconds). Uh oh. That throws all the timing off because previously at a time of 1.001 second (overall), that'd be just a tiny bit into the 2nd iteration, but now you've stretched out the timing so that 1.001 seconds is a little past the halfway point of the first iteration. Previously 1.5 seconds would be halfway through the 2nd iteration, but now it's 75% into the 1st iteration. And then imagine what happens when it hits the new end (2-second spot) on the timeline and tries to repeat and you add more to the timeline. 

 

It's not a bug - it's just the nature of repeating animations. 

 

Like I said, I think you're probably over-engineering this and there's really no need to keep adding new animations to the same timeline, making it grow infinitely longer and more full with animations that you don't even need anymore (memory). It's much cleaner to just do something like: 

function doAnimation() {
  let tl = gsap.timeline({ onComplete: doAnimation });
  tl.to(...);
}
doAnimation();

No more infinitely growing. Simple. Clean. Repeats forever but you can have new, dynamic values in there every time. 

 

2 hours ago, fcdobbs said:

Also, every time the callback executes the .iteration() logs as 1.

That's because you're passing in the value of 1 every time: 

// at the time you create this Array, vineTL.iteration() returns the number 1 which is what you're putting in your Array
vineTL.eventCallback("onStart", addStem, [vineTL, vineTL.iteration()]);

That's what you stored in your Array. 

 

I hope that helps clear things up. 

  • Like 1
Link to comment
Share on other sites

Thanks!

 

With a recursive call to the function, I was able to get the vineTL to rebuild itself from scratch without repeatRefresh.

 

Based on reviewing the forums, it looks like animating the points on a motionPath after it’s assigned to a timeline isn’t possible in GSAP.

 

Is it possible to define a property function in a tween that uses a value from a motionPath applied to a generic object in a different timeline based on position?

 

I thought I had figured out a syntax that would link the scale property to the x value of a second timeline with a motionPath based on position:

scaleX: () => {motionPath2.progress(vineTL.progress()); return p2.x},

 

See the Pen wvEMyEB by fcdobbs (@fcdobbs) on CodePen

 

It looks like the tween is animating between two static values.

 

Or is there a way to assign a Bezier curve to a formula without using motionPath and retrieving, for example the x for a given y or vice versa?

 

Thanks again for all of your help.

Link to comment
Share on other sites

I read your post 3 or 4 times and I'm still a little fuzzy. Let me clarify a few things...

  1. When you define a function-based value in an animation, that just gets called when the tween first renders. The purpose is to get the destination value. It locks that in and interpolates between the start/end values over the course of the tween. It sounds almost like you were expecting it to constantly call that function and redirect itself to whatever that function returns, 60 times per second for example. That would be pretty terrible for performance. 
  2. If your goal is to animate the path itself (like all the individual points that make up the path) AND you've got other motionPath tweens that are based on that path, it would be expensive to do that. When you create a motion path, it has to parse it, figure out all the points and then also create an internal map that allows for clean, steady interpolation across that path because the very nature of Beziers is NOT steady. Without the interpolation map, you'd see things speed up and slow down along the path based on handle placement. So it sounds like you're talking about re-creating the motionPath 60 times per second, animating its individual points, and then making it do all of its measurements again, rebuilding its interpolation map, PLUS you want a bunch of other motionPath tweens to also redo their paths and remap positions 60 times per second. Is that right? I'm just saying you may not realize how much effort that requires. 
  • Like 1
Link to comment
Share on other sites

Thanks.

 

I’d like to apply a scale transformation and x and y offsets that vary depending on an element’s position in a motionPath timeline.

 

Can an element have two motionPath tweens with identical start times, durations, and eases, so that the animations overlap?

 

If not, I believe I can to apply a motionPath tween to a parent svg g or div.

 

Can I replace x and y with an array other element properties in the cubic bezier coordinate format with anchor, two control points, anchor, two control points, etc. in a motionPath attached to a tween with identical duration, ease and start time and end up with contemporaneous transformations of those properties to either the same or a parent element?

 

I tried a couple of versions of replacing x: with scale: in the array of coordinates in the Vine with random leaves example.

 

I thought maybe if I replaced x: with scale and y: with a property that doesn’t exist, GSAP would use the scale values with the same inflection points and rates of change as the x value in the original motionPath.

See the Pen xxaVVzP by fcdobbs (@fcdobbs) on CodePen

 

The scale transformations don’t appear to overlap with the stem curve, but maybe I made a different mistake.

 

Thanks again.

Link to comment
Share on other sites

I think you might be overcomplicating this, but I'm still having a tough time weeding through all the code and deciphering exactly what you want. Can you just provide a super simple demo with only one path, one leaf and without all the extra dynamically-building of all the SVG, randomizing of things, etc.? I think the more we focus and simplify, the better the chances we'll connect on a solution. For example, "here's a simple path and a leaf that I want to animate along the path, but the leaf should start out at a scale of 1, grow to 2 in the middle of the path, and end at 0.5" (or whatever). 

 

Or maybe you want the motion path itself to be morphing while the leaf moves along it(?) Again, the simpler the demo and the more concise you can make your explanation of what you want, the better. 

 

44 minutes ago, fcdobbs said:

Can an element have two motionPath tweens with identical start times, durations, and eases, so that the animations overlap?

Animating different properties? Sure. Absolutely.

 

Also, to be clear, when you pass in a motionPath, like motionPath: {path: [...]}, it almost sounds like you think it's going to animate the path itself, like the objects inside that Array(?), altering their properties. That is not how it works. That is just that data it uses to calculate where the thing goes (and again, it builds the interpolation map to smooth out the motion, etc.) It never touches the raw data you pass in. That would be problematic, like if you were gonna use that elsewhere too. 

 

I see you were trying to use "fakeProp", but since the target of your tween doesn't have any such property, that won't work. It'd be fine, though, if you were using a generic object with a "fakeProp" property. You can use it to animate pretty much whatever you want. 

  • Like 1
Link to comment
Share on other sites

Thanks.

I think I figured it out.

I used strokeWidth on an element with no stroke as an ersatz y coordinate.  Now the motionPath on scale and the motionPath on x appear to have the same inflection points and rates of change.

I’d be curious if there’s an easier way to fake 3D motion on a nested SVG element.

Thanks again for all of your help!

 

See the Pen PodzQey?editors=0010 by fcdobbs (@fcdobbs) on CodePen

Link to comment
Share on other sites

Glad you found a solution. 

 

I don't have much time to dig into all your code and I don't quite understand what you mean by "...to fake 3D motion on a nested SVG element", but if you still need help, please make sure you provide the simplest possible demo with as few elements as possible. It looks like you're still dynamically creating stuff. Not the end of the world - I'm just giving you tips that'll help get you a more quick and accurate answer.

 

Are you saying that you think the x animates differently if you add [unused] a strokeWidth value to each object in the Array? I didn't see any difference. MotionPathPlugin does correlate x and y values in a special way to calculate lengths/points, but not other properties. 

Link to comment
Share on other sites

Thanks.

You’re right.  If I change the order of values for the first coordinate, the paths no longer line up.  GSAP must be making the Bezier curve calculation differently based on the units.

 

Here I added the leaf and the leafGroup to the DOM and changed the first coordinate value for the first two control points on both curves, and the inflection points in scale and in x no longer line up:

See the Pen JjaEPoq?editors=0010 by fcdobbs (@fcdobbs) on CodePen

Maybe the original pattern of each first coordinate value for both control points forming a straight line to the next anchor point first coordinate value represents a special case of the curves lining up according to both calculations.

Is it possible to adjust the control point values in the x and y calculation for the curviness factor used in the calculation for properties that are not x or y?

If I can adjust the control points for the x and y calculation, maybe I can get the inflection points and rates of change to line up for both kinds of Bezier curve.

 

Thanks again.

Link to comment
Share on other sites

8 hours ago, fcdobbs said:

You’re right.  If I change the order of values for the first coordinate, the paths no longer line up.  GSAP must be making the Bezier curve calculation differently based on the units.

Sorry, perhaps it's because it's 2am and my brain is turning off, but I read your response a few times and it's as clear as mud to me :) (probably my fault). I mean of course if you change the values of the first coordinate, the path would not line up...I was just asking why you added an [unused] strokeWidth property to all the objects. And what difference do you think it made (and what evidence are you looking at)? Your demo still seems way more complicated than it really needs to be to illustrate the core problem. 

 

When calculating the bezier control points, "x" and "y" (ONLY!) are correlated as 2D so that it can plot things in 2D space and make the control points as such. For example, {x: 0}, {x: 100} would have a length of 100 whereas {x: 0, y: 0}, {x: 100, y: 100} would have a length of 141.42. There are no other properties that are correlated as such, so I don't know why you think you got different results by adding strokeWidth...or did I misunderstand?

 

Possible idea: if you're trying to correlate 2 other properties, maybe just use "x" and "y" for those (to get the automatic correlation) and then apply them with gsap.quickSetter() in the onUpdate to the "real" properties you want (like scale or whatever). Just a thought. 

 

If you're still stuck, maybe it'd help if you could explain your end goal as if I'm 5 years old :)

Link to comment
Share on other sites

Thanks!

I’ll take a look at the gsap.quickSetter()/onUpdate solution.  Maybe this will allow me to make the scale and y values a function of the x values.

Did the GSAP 2 bezierplugin also only work on properties x and y, or did it calculate the same curve no matter what the units were labeled?

Am I perhaps missing a simpler way of adding a z-axis to the motionPath of a nested SVG element?

Thanks again for all of your help.

Link to comment
Share on other sites

14 hours ago, fcdobbs said:

Did the GSAP 2 bezierplugin also only work on properties x and y, or did it calculate the same curve no matter what the units were labeled?

The old BezierPlugin only correlated x, y, and z (and I think top and left). 

 

14 hours ago, fcdobbs said:

Am I perhaps missing a simpler way of adding a z-axis to the motionPath of a nested SVG element?

I'm still fuzzy on exactly what you're asking here. You want to add a "z" property to all your objects? And what significance does "nested" have? 

Link to comment
Share on other sites

Thanks.

 

Can you direct me to examples of how z translations are correlated to x and y in arrays that use the motionPathPlugin?

 

The reason I mentioned nested SVG's is that I believe the coordinate units will be in the parent SVG's viewbox units, and the motionPaths will scale with resizing of the parent SVG.

 

Thanks again.

Link to comment
Share on other sites

Is there an equivalent in the motionPathPlugin to this description from the bezierPlugin docs?

 

And if you need to affect multiple rotational properties (like in 3D tweens where the Bezier is going through x,y,z points which could affect rotationX, rotationY, and rotationZ), you can use an array of arrays, like[["x","y","rotationZ",0,false], ["z","x","rotationY",0,false], ["z","y","rotationX",0,false]].

Link to comment
Share on other sites

1 hour ago, fcdobbs said:

Is there an equivalent in the motionPathPlugin to this description from the bezierPlugin docs?

No. 

 

When we crafted GSAP 3, we spent a bunch of time figuring out where there was waste and how to make things cleaner, more intuitive, etc. MotionPathPlugin is significantly easier to use and overall more capable than BezierPlugin, but one of the things left on the cutting floor (because almost nobody used it) was the correlation of properties beyond x/y (basically 3D stuff). It allowed us to streamline things. To add z support, it's quite a lot of extra code we'd have to add, so it's no small task. I'm still very confused about what exactly you're trying to do and why you think you need to correlate extra properties for SVG animations. 

 

I'll re-assert my request: 

On 2/28/2023 at 1:58 AM, GreenSock said:

If you're still stuck, maybe it'd help if you could explain your end goal as if I'm 5 years old :)

And a much simpler CodePen illustrating what you're trying to do...that'd go a long way. You've got a curved path with a leaf traveling on it...but I don't understand what that has to do with 3D, property correlation, nesting, etc. 

Link to comment
Share on other sites

Thanks.

 

For a motionPath applied to properties other than x or y, like scale, will the target pass through the values in the array always at positions equally distributed across the duration of the tween, or is there a way to control how those values are distributed across the duration of the tween?

 

Also, is there a syntax under which z can be a property used in a motionPath array?

 

I'd like to add depth to the tween of the leaf on the motionPath.

Link to comment
Share on other sites

4 hours ago, fcdobbs said:

For a motionPath applied to properties other than x or y, like scale, will the target pass through the values in the array always at positions equally distributed across the duration of the tween, or is there a way to control how those values are distributed across the duration of the tween?

No, MotionPathPlugin plots a smoother path through the values rather than just going from one to the other. What you're describing there is super easy to do with the keyframes feature, like: 

gsap.to(element, {
  keyframes: {
    x: [0, 185, 500] // <- equally spaced
  }
});

 

4 hours ago, fcdobbs said:

Also, is there a syntax under which z can be a property used in a motionPath array?

Yes, with the same Array-based syntax you were already using. But keep in mind that you cannot do 3D inside an SVG. The spec doesn't allow it. That's not a GSAP thing, it's just an SVG thing. 

Link to comment
Share on other sites

Thanks.

 

I was able to map scale for a nested SVG element to a bezier curve using the modifiersPlugin.

 

Codepen appears to become unresponsive when I apply this structure to an element that is also on a morphSVG timeline.

 

Is there any reason that a gsap.quickSetter()/onUpdate structure would use less processing power than the modifiersPlugin?

 

I'm also considering building a timeline in a loop of tweens that sample the x values of the generic object at arbitrary intervals, for example every 100th or 1000th of the motionPath progress.

 

Or using drawSVG to calculate the length of the segments between each anchor point, assuming that the progress % is equal to the length %, and only sampling the x values of the generic object at the anchor point progress values.

 

Would either of these structures require significantly less processing power?

 

Thanks for your help!

 

See the Pen xxaLJZy?editors=0010 by fcdobbs (@fcdobbs) on CodePen

 

 

Link to comment
Share on other sites

4 hours ago, fcdobbs said:

Is there any reason that a gsap.quickSetter()/onUpdate structure would use less processing power than the modifiersPlugin?

 

That's very unlikely. Tough to say for sure without viewing your code side-by-side.

 

4 hours ago, fcdobbs said:

I'm also considering building a timeline in a loop of tweens that sample the x values of the generic object at arbitrary intervals, for example every 100th or 1000th of the motionPath progress.

For what purpose? I'm really struggling to understand what you're doing and why you're doing it. Are you looking for a way to measure the length of a Bezier curve? Individual parts of one? May I ask why? 

 

4 hours ago, fcdobbs said:

Or using drawSVG to calculate the length of the segments between each anchor point, assuming that the progress % is equal to the length %, and only sampling the x values of the generic object at the anchor point progress values.

DrawSVGPlugin can tell you the length of a <path> or some of the basic SVG shapes, but aren't you trying to measure only between individual anchors inside the Bezier? (Again, I don't really understand why, but I'm trying to grasp why you'd use DrawSVGPlugin for any of this)

 

5 hours ago, fcdobbs said:

Would either of these structures require significantly less processing power?

 

I suspect there may be an easier way to do what you're trying to do, but since I don't really understand it I'm not able to offer much advice. Sorry :(

Link to comment
Share on other sites

Thanks.

 

I'm adding scale transformations to the motionPath of a nested SVG element to give the illusion that the element is moving through 3D space.

 

The modifiersPlugin works well for this.  I'm wondering if it's possible to achieve the same effect with less processing power by tweening the scale through fewer scale values along the bezier curve, instead of on every tick.

 

I couldn't figure out how to determine at what position values the element will pass through the anchor points without measuring the length of the path between anchor point segments using drawSVG.

 

I could also sample the x values of the generic object every at every .01 position value or every .001 position value and tween the scale through those values at those positions.

 

This would reduce the number of scale calculations, but it might not save much processing power if the rendered curve is almost identical.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...