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

Hi,

 

I'm creating an animated vine with the drawSVG plugin, with dynamically created leaves. The stem is a vertical bezier curve (SVG path) that I'm creating using a few random numbers. I'm then using a loop to create a bunch of leaf elements, which I then am trying to place along the curved path. The 'y' position isn't a problem, but I'm not quite sure what a good solution is for the 'x' position of the leaves to ensure that they're placed along the vine's stem. 

 

Looking at the Bezier plugin, I was wondering if there's a way to use the same path data I've created for the stem and use it as a motion path for the leaves, only instead of animating their position along the path, simply place them at a random position along it. I haven't used the Bezier plugin before, so I wasn't sure how doable that is, if at all. Anybody know if this is an easy undertaking, or should I consider a different approach?

 

-darin

See the Pen ac328b607b6775c329e4313bad317607 by dsenneff (@dsenneff) on CodePen

Link to comment
Share on other sites

This thread is full of good info about plotting along a Bezier curve. Maybe it can help.

It's not exactly what you need since you don't want to animate the position, but this thread also had some neat Bezier curve ideas in it.

Hopefully that helps a bit. Happy tweening.

:)

 

  • Like 4
Link to comment
Share on other sites

Thanks, @PointC! Very helpful.

 

I have a solution working currently using Jack's ChoppedEase from the second link you posted.

 

I essentially created a separate timeline for the leaves. Then in my loop where I create each leaf element, I add a .01s tween to that timeline for each leaf along the same motion path used by the vine, with the ChoppedEase value randomized. That essentially works as a "set" method to place the leaves perfectly along the path at random intervals.

 

Once I play with it a bit more and know it's all working, I'll add a new CodePen link to this thread in case it helps somebody in the future. 

 

-darin

  • Like 2
Link to comment
Share on other sites

One relatively easy way would be to create a generic object with x/y properties (coordinates) and tween that along the bezier path, but pause that tween and just set its progress() value and see whatever the corresponding x/y values are on that generic object at that point. 

 

Here's an edited version of your codepen that shows you the concept, and I made the leaves wait to animate until the vine reaches the position accordingly: 

 

Does that help? 

 

  • Like 4
Link to comment
Share on other sites

Reminds me of something I did several years ago, although I didn't use Beziers for the stems because I was going for a rougher look.

 

See the Pen gpGEye by osublake (@osublake) on CodePen

 

 

If you're interested, here's a tutorial showing how to grow vines around a lattice. The site has been redesigned since I last saw it, and some of the demos appear to missing. Such a shame because the last demo was really cool.

 

https://www.maissan.net/articles/simulating-vines

 

 

 

  • Like 5
Link to comment
Share on other sites

On 9/5/2018 at 5:37 AM, GreenSock said:

One relatively easy way would be to create a generic object with x/y properties (coordinates) and tween that along the bezier path, but pause that tween and just set its progress() value and see whatever the corresponding x/y values are on that generic object at that point. 

 

Here's an edited version of your codepen that shows you the concept, and I made the leaves wait to animate until the vine reaches the position accordingly: 

 

Does that help? 

 

 

I get an error when opening this pen in FireFox: 'TypeError: d is null' pointing a the line 24114 of TweenMax .min.js

 

:(

 

Link to comment
Share on other sites

32 minutes ago, Dipscom said:

I get an error when opening this pen in FireFox: 'TypeError: d is null' pointing a the line 24114 of TweenMax .min.js

 

Good catch! That's just because the SVG element wasn't added to the DOM before there were transform-related values set, so it's just a matter of flip-flopping these lines:
 

//OLD: 
TweenMax.set(leaf, {attr:{"d": leafPath}, transformOrigin: "left center"});
container.appendChild(leaf);

//NEW:
container.appendChild(leaf);
TweenMax.set(leaf, {attr:{"d": leafPath}, transformOrigin: "left center"});

 

Firefox throws errors when you try to grab certain values on an SVG element that's not visible or in the DOM (like getBBox()). 

  • Like 2
Link to comment
Share on other sites

14 minutes ago, Dipscom said:

I get an error when opening this pen in FireFox: 'TypeError: d is null' pointing a the line 24114 of TweenMax .min.js

 

Pointing out the error in a minified file does not help. Try using a regular file and expanding the error. Human readable code!

https://unpkg.com/gsap@2.0.2/umd/TweenMax.js

 

GSAP can't do it's hack to set the transform origin because the leaf isn't in the DOM.

// BAD
function createLeaf(container) {
  let leaf = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  TweenMax.set(leaf, {attr:{"d": leafPath}, transformOrigin: "left center"}); 
  container.appendChild(leaf);
  return leaf;
}

// GOOD
function createLeaf(container) {
  let leaf = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  container.appendChild(leaf);
  TweenMax.set(leaf, {attr:{"d": leafPath}, transformOrigin: "left center"});   
  return leaf;
}

 

  • Like 4
Link to comment
Share on other sites

2 hours ago, OSUblake said:

Pointing out the error in a minified file does not help. Try using a regular file and expanding the error. Human readable code!

 

Harsh!

 

I may have been sloppy. Fine. But I am sleep deprived.

 

It wasn't me who tried to reach for an element before it was in the DOM. ?

 

  • Like 1
  • Haha 1
Link to comment
Share on other sites

  • 4 years later...

Hi,

 

I'm attempted to convert the Vine with random leaves codepen to GSAP 3.

 

My guess is there is something about the conversion from the BezierPlugin to the MotionPathPlugin that I don't understand.

 

See the Pen 121dd702a2983bc308a573bca6b072e4 by GreenSock (@GreenSock) on CodePen

 

I tried replacing bezier with MotionPath and updating the TweenMax/gsap and TimelineMax/gsap syntax:

See the Pen rNZBegV?editors=1111 by fcdobbs (@fcdobbs) on CodePen

 

Any help in understanding the difference between bezier and MotionPath and migrating this pen to GSAP 3 would be appreciated.  Thanks!

Link to comment
Share on other sites

Hello @fcdobbs

 

Instead of values in your motionPath object, just use path and you seem to be good to go.

 

// motionPath = gsap.to(p, {duration: 1, motionPath:{type:"cubic", values:stem.motionPath}, paused:true});

motionPath = gsap.to(p, {duration: 1, motionPath:{type:"cubic", path:stem.motionPath}, paused:true});

 

https://greensock.com/docs/v3/Plugins/MotionPathPlugin

 

See the Pen vYzBJxq by akapowl (@akapowl) on CodePen

  • Like 1
Link to comment
Share on other sites

Super.  Thanks!

 

Changing the BezierPlugin parameter 'value', to the motionPath parameter 'path' got the pen drawing.

 

Any idea how to set it to run the getRandomInRange function on child tweens on repeat?

 

I tried adding repeatRefresh: true, but it draws the same stem and same leaves on repeat:

 

//vineTL = gsap.timeline({paused: true, delay: 1, repeat: -1, repeatDelay: 3, yoyo:true});
    vineTL = gsap.timeline({paused: true, delay: 1, repeat: -1, repeatDelay: 3, yoyo:true, repeatRefresh: true});

 

Thanks again.

Link to comment
Share on other sites

Thanks.  I'll replace the getRandomInRange function in this pen with random().

 

Any idea why repeatRefresh doesn't cause the getRandomInRange function to run again on repeat for the leaf parameters that are set with this function in the vineTL?

 

It looks like setting the stem values aren't part of the timeline that repeats.  I expected all of the functions in the vineTL to run again on repeat.

Link to comment
Share on other sites

 

The way I see it, the positioning of the leaves is being done once in a loop for each of the leaves in a gsap.set() call, which is totally independent of the timeline, based on a random progress variable, that also only gets populated/overridden once for each of the leaves when things are being set up. That progress variable will never again change, once all of the leaves are processed in the loop. Also will the leaves never be .set() to a different position than they were before, so a repeatRefresh will do nothing much here, I suppose.

 

Since the position of the tween for each leaf on the timeline would then of course change (because it is dependent on the progress variable's value), I think repeatRefresh is not the way to go here anyway. Although I might as well be wrong with that, I think you might have to clear/invalidate the timeline every other repetition, do what is neccessary to randomly reposition the leaves (visually), and then re-create the timeline's tweens, with the tween for each leaf positioned on the timeline with respect to the new random value you assign for it.

 

Since that example is rather complex and it is kind of hard to see through all the logic from outside, especially with an otherwise cluttered mind, all that is nothing I can do for you though. Maybe I'm even totally wrong and somebody else can offer a better viewing angle on this.

 

Link to comment
Share on other sites

Thanks.  It makes sense to me that the position values are set, and the scales are set to zero once on the global timeline.

 

I'm confused as to why the functions that are included in the tweens that make up the vineTL aren't executed on repeat.

 

I would expect the position to be set on the first iteration, and the tweens to have different "to" values for scaleX, scaleY, and rotation on repeats with repeatRefresh set to "true".

 

      vineTL.to(leaf, {duration:2,
      //make some leaves point left, some right (randomly)
      scaleX: (Math.random() < 0.5 ? getRandomInRange(1, 1.5, 2) : getRandomInRange(-1, -1.5, 2)),
      scaleY: getRandomInRange(1, 1.5, 2),
      rotation: getRandomInRange(-20, 20, 0),
      ease: Circ.easeOut},
    //don't start it until the vine gets to this point
    duration * progress);

 

I'm not sure what breaks the parent-child relationship between the vineTL timeline and all of the "to" tweens on the leaf objects that are on the vineTL timeline.

 

I'm also wondering if transformOrigin affects the position of the d attribute in the createLeaf function:

gsap.set(leaf, {attr:{"d": leafPath}, transformOrigin: "left center"});

 

The transformOrigin is set a second time for each leaf at 0 on the global timeline, so it's unclear to me if the first transformOrigin is required for the d attribute, or if it's redundant:

gsap.set(leaf, {x:p.x + svgWidth/2, y:p.y - leaf.getBBox().height / 2, scaleX: 0, scaleY: 0, rotation: getRandomInRange(-15, 15, 0), transformOrigin:"0% 50%"});

 

There are several features of this pen that could be useful to me.

 

Thanks again.

 

 

Link to comment
Share on other sites

It's because NONE of these are functions: 

// regular values
scaleX: (Math.random() < 0.5 ? getRandomInRange(1, 1.5, 2) : getRandomInRange(-1, -1.5, 2)), 
scaleY: getRandomInRange(1, 1.5, 2), 
rotation: getRandomInRange(-20, 20, 0), 

You're just passing in the returned value of those function invocations.

 

If you want things to get refreshed, you've gotta use function-based values like: 

//function-based values
scaleX: () => (Math.random() < 0.5 ? getRandomInRange(1, 1.5, 2) : getRandomInRange(-1, -1.5, 2)), 
scaleY: () => getRandomInRange(1, 1.5, 2), 
rotation: () => getRandomInRange(-20, 20, 0), 

 

6 hours ago, fcdobbs said:

I'm also wondering if transformOrigin affects the position of the d attribute in the createLeaf function:

gsap.set(leaf, {attr:{"d": leafPath}, transformOrigin: "left center"});

No, transformOrigin has absolutely nothing to do with the "d" attribute. It solely affects transforms. 

 

Does that clear things up? 

  • Like 2
Link to comment
Share on other sites

Thanks.

 

There's still something I don't understand about repeatRefresh.

 

I updated the syntax to the function syntax above, but scaleX, scaleY, rotation are not refreshing on repeat of the vineTL timeline.

 

I also removed the redundant set transformOrigin:

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

 

Is there something else about the way the vineTL timeline is built that makes repeat work, but not repeatRefresh?

 

Thanks for your help.

Link to comment
Share on other sites

Sorry about the confusion there. You actually hit an edge case that exposed a bug - if you have a repeating timeline with yoyo: true and you also have a repeatDelay, it didn't do the repeatRefresh. That should be fixed in the next release which you can preview at https://assets.codepen.io/16327/gsap-latest-beta.min.js

 

(you probably need to clear your cache)

 

Better? 

  • Like 1
Link to comment
Share on other sites

It works!  Thanks.

 

I have some related questions about performance with repeatRefresh.

 

My functions calculating the tween properties will be longer than in this example.

 

Is there any reason making the calculations outside of the tween, feeding them into an array, and having the tween pick a value either randomly or by remainder of the repeat iteration would be less resource intensive than repeatRefresh with functions?

 

Would the answer depend solely on how many times the property values are calculated, so that any unused values populating the array when the animation is terminated represent wasted resources?

 

Also, if I find six variations is sufficient, would it be less resource intensive to generate a timeline with all six variations and repeat the 6x longer timeline?

 

Thanks again.  This example has several features I can use.

Link to comment
Share on other sites

2 hours ago, fcdobbs said:

Is there any reason making the calculations outside of the tween, feeding them into an array, and having the tween pick a value either randomly or by remainder of the repeat iteration would be less resource intensive than repeatRefresh with functions?

 

Would the answer depend solely on how many times the property values are calculated, so that any unused values populating the array when the animation is terminated represent wasted resources?

I read that a few times and I'm still pretty lost, sorry.  Maybe it'd help if you provided basic pseudo code. Minimal demos are always best of course. 

 

2 hours ago, fcdobbs said:

Also, if I find six variations is sufficient, would it be less resource intensive to generate a timeline with all six variations and repeat the 6x longer timeline?

I doubt you'd ever notice any real-world performance difference whatsoever. The JS in cases like this accounts for a tiny fraction of the overall load on the CPU. In my experience, graphics rendering is often well over 90% of the load (often over 99%). 

 

When in doubt, just test. If I were in your shoes and I was really curious, I'd do it both ways and then measure. But I cannot think of any reason inside GSAP that'd cause one to be significantly faster than the other. 

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...