Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
ad_bel

Material design effects with Bezier curve

Recommended Posts

Hi,

 

I am trying to reproduce some animations based on google material design motion: 

 

https://material.io/guidelines/motion/movement.html

 

I've got a decent start on making a floating action button morph into a card and here is what I have thus far:

 

 

Although it's ok, I have 2 questions that maybe someone can help me with to make this better:

 

1. For the Bezier plugin curve I am doing this:

 

var dw = window.innerWidth;
var dh = window.innerHeight;
 
var mw = dw * 0.1;
var mh = dh * 0.5;
 
var points = [
    {x:0, y:0},
    {x:mw, y:mh}, //Is there a better way to do this?
    {x:(dw/2)-28, y:dh-130},
  ];
 
which is a hack to find the control points. Is there a better way to do this? The multiplication factors are hard coded with a value chosen based on the end point. Since my example is in the center/bottom of the screen it looks ok but if I change the location it doesn't look as good :/ All the examples I have seen for this plugin were to animate along a known path but in this case the path needs to be derived based on the end point.
 
2. When I animate the opening of the card it seems pretty static. Is there a way to make it look more like the following?
 
 
I tried a few things like starting the expanding of the card animation as the button is moving along the curve but that messed up the reverse animation :(
 
Thanks!

See the Pen wJMrYq by ad_bel (@ad_bel) on CodePen

Link to comment
Share on other sites

Hi @ad_bel,

 

There's a magic number that you can use to approximate a quarter-circle arc as cubic Bezier. Well, I've seen a couple, but I've been using the one from this article.

var kappa = 0.551915024494;

Just multiply that number by the radius of the arc to find the distance of the control point from the center. Check it out!

 

See the Pen oZxobX?editors=0010 by osublake (@osublake) on CodePen

 

 

Link to comment
Share on other sites

To make it look better...

 

First, you longer need to include that easing script I wrote. You can do Bezier easing with the new CustomEase.

CustomEase.create("standardCurve", "0.4, 0.0, 0.2, 1");

TweenLite.to(foo, 1, { x: 100, ease: "standardCurve" });

For the expand/ripple effect, I would use a minimum of 2 elements similar to the hero effect in this post...

https://greensock.com/forums/topic/14206-full-screen-a-div/?p=60466

 

You'll want the original fab (floating action button) to move into the container, and then hide it, revealing the circle used for the ripple effect. For a ripple effect, you don't animate the border radius. Instead, you animate it's radius to reach the furthest corner with overflow hidden on the container. Here's a post about that...

https://greensock.com/forums/topic/16072-circles-from-size-0-to-fully-cover-the-screen-–-performance-and-concept/?p=70592

 

However, as this isn't a click-based ripple, there's no need to find the furthest corner as you're moving it to the center of your container, so you can calculate the radius like this...

var dx = containerWidth  / 2;
var dy = containerHeight / 2;
var radius = Math.sqrt(dx * dx + dy * dy);

To figure out the start time for the ripple animation with regards to the motion path, you can do something like this to find the time/progress value along a Bezier curve. You'll want to start the ripple animation when the fab is inside the container it's going to fill.

 

See the Pen bZJOgz?editors=0010 by osublake (@osublake) on CodePen

 

 

Hope that helps!

.

  • Like 2
Link to comment
Share on other sites

Just for clarity, most of the code in my

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

is for canvas rendering. For the arc calculation, this is all you need. p0 is the start point, and p3 is the end point. p1 and p2, are the control points, which is what the following code sets.

var kappa = 0.551915024494;

var dx = p3.x - p0.x;
var dy = p3.y - p0.y;
  
if (p3.y > p0.y) {
    
  p1.x = p0.x;
  p1.y = p0.y + (dy * kappa);  
  p2.x = p3.x - (dx * kappa);
  p2.y = p3.y;
    
} else {
    
  p1.x = p0.x + (dx * kappa);
  p1.y = p0.y;  
  p2.x = p3.x;
  p2.y = p3.y - (dy * kappa);
}
 

.

Link to comment
Share on other sites

Combining some of the stuff I mentioned might not be clear, like getting the start time from the bezier, so I'll make a demo for you...

Link to comment
Share on other sites

Understanding how to get start times from that demo I linked to probably wasn't clear, so I made a better example.

 

Pass in a distance value to the getDelay function, and it will return what time your object will pass that point. Kind of like ScrollMagic, but for Beziers.

 

 

QPdMWxr.png

 

 

A positive value will be inside the bounding container, as pictured above. A negative value will be before the container. I only set it up for one value, but it would be easy to change it to handle multiple points.

 

Here's a demo with it running in slow motion so you can see what's going on. 

 

See the Pen BWzKjB?editors=0010 by osublake (@osublake) on CodePen

 

  • Like 1
Link to comment
Share on other sites

And here's a version that runs at normal speed, and you can click...

 

See the Pen oZLLYg?editors=0010 by osublake (@osublake) on CodePen

 

 

 

I didn't use a clone for that demo, although I should have. If you use a negative padding value, you might notice a slight clipping issue when the circles are merging. Using a clone that is appended to the body makes it much easier to control issues like that.

 

TpCWmuW.png

 

 

 

If you're looking for more ways to do Material Design motion, here's a scroll header I made a couple of weeks ago. Everything is animated with transforms, so its very performant. You can use that same technique for other components, like a card expanding.

 

See the Pen NdEONL?editors=0010 by osublake (@osublake) on CodePen

 

  • Like 4
Link to comment
Share on other sites

Hi Blake,

 

Sorry I have not been ignoring you - I was just working on a new pen based on your previous posts. Wow this is incredible stuff... seriously mind blown :)

 

I actually did not have a problem finding that control point. That worked very well and it looks very natural! Can't lie and say I understand the math though ;)

 

What I was trying to work on was the hero piece with 3 elements: the from, to and the clone. This impressed me the most and never thought to do it that way. That technique solves so many issues I was having with positioning (especially in responsive design) because I can always keep the to and from elements in the regular document flow (and can easily position using flex). This seems like it should be the recommended way to build animations for ui developers... unless it is and I'm just late to the party lol

 

What I am trying to do next is to make it so I can make something like your hero demo where it morphs into multiple steps. For example button goes from one card to the bottom, then clicked goes to a card on the side, then maybe opens full screen or something to that effect. Some questions:

 

1. Would you create a single timeline for those interactions or one for each. I am building this in vue.js and it is component based. I was approaching it from the multiple timelines idea and recreate as necessary but would like to hear your thoughts on this.

2. Should I remove the cloned element or simply hide it? Is there an advantages to either approach?

 

I'm on my phone now but I will study the code of you new pens when I have acres to my computer. I will also post my pen when it is more stable (disclaimer: it probably won't be as good as your latest posts lol).

 

Again thanks so much for your help! This is simply fantastic!

Link to comment
Share on other sites

I forked your project and here is what I have so far:

 

See the Pen vxKpgW by ad_bel (@ad_bel) on CodePen

 

I made some changes so that the ripple is hidden at the end of the animation otherwise you cannot see the card content ;) Also made it so the card is not visible before hand. I think it should be a blank place holder and then morph into a card (or whatever other components you want).

 

I added dotted lines to show different targets to animate to. For simplicity, to switch the location of where it animates to I just set the cardNum property to 1 or 2. When I animate to card 1 it looks fine. When I animate to card 2 the button starts to scale immediately. I think this is because of the delay function - it appears as if the value is 0. I haven't dug deep into this yet but maybe this is obvious to you and you can save me some time :)

 

In my card as you can see I added some actions i.e. expand button, previous, next and "animate to next box" button. Ideally I would like to be able to do multiple transitions but in order to keep the code from getting crazy, I  think it needs to be made modular. Next I was thinking of implementing you idea of using the cloned node. This way each action has its own animation timeline (including reverse). What do you think? Is this the right approach?

 

If this works, I would think it would be easy to do the animation and then leverage the transition hooks of whatever framework you are using. In my case it would be this:

 

https://vuejs.org/v2/guide/transitions.html (scroll to transitioning between components)

 

but if you were using React for instance you can use the ReactTransitionGroup. Basically do the complex animations in gsap and use the hooks of your specific framework to transition views when it's complete

Link to comment
Share on other sites

Updated version. Animating the cloned node instead of the actual element

 

See the Pen mWrpbW by ad_bel (@ad_bel) on CodePen

 

I ended up doing the following (notice I set the values of circle & circleClone twice) because I could not get it to the right state on the reverse. Not sure if there is a better way but it works :/

 

  tl.set(circle, { autoAlpha: 1 })
    .set([card, circle], { autoAlpha: 0 })
    .set(circleClone, { autoAlpha: 0 })
    .set([ripple, circleClone], { autoAlpha: 1 })

 

Also notice I did not remove the clone from the DOM. If I did I could not get the reverse to work. I do think I would need to be able to reset it (to its original position) though for when that button is available again. For example, do other animations that close the box and the circle button is available again. I would not want the clonedNode sitting in the reverse state.

 

This could also be made more modular maybe if this was turned into a hero function with a signature something like (fromElement, toElement, timeline).

 

I will probably see if I can animate to other positions next using clones as well :)

Link to comment
Share on other sites

You're not late to the party about using clones. Outside of some stuff made by Google, I've never seen anybody do that. I'll try to make a better example of working with clones later.

 

And all this stuff animation can be written a modular fashion. I was just showing the concept, trying to be vanilla as possible, but I spent several months working out a lot of this motion stuff for a project in Angular1. I haven't used Vue, but from I can tell, it looks almost exactly like Angular1, so a lot of what I'm going to explain should work the same. 

 

Most of what I did is based on some old Polymer 0.5 stuff, like these demos. Click the yellow, General Knowledge box in the second demo.

https://polymer-topeka.appspot.com/

https://docs-05-dot-polymer-project.appspot.com/0.5/components/core-animated-pages/demos/quiz1.html

 

Doing animations like that is really complicated because of the nesting, so I kept the animation code separate from my components. I created an animation service that would manage everything. In my animation hooks, I would pass in the element to the animation service.

enter(element, done) {
  heroAnimator.enter(element, done);
}

leave(element, done) {
  heroAnimator.leave(element, done);
}

From there, the service can figure out what needs to be done by looking for certain properties on the element. So if you are animating a button into a card, it will look for matching hero ids between the two elements, and create hero animations for any matching ones. So in this example, an animation will also be created for the icon.

<material-button hero-id="hero">
  <div class="add-icon" hero-id="icon"></div>
</material-button>

<material-card hero-id="hero">
  <div class="clear-icon" hero-id="icon"></div>
  <div class="card-content" hero-id="content"></div>
</material-card>

That's a simplified look at how I set it up to work. I had other stuff on the elements that the service would look for, like different types of animations (crossfade, slide-in, etc) or settings.

 

 

.

  • Like 4
Link to comment
Share on other sites

Hi Blake,

 

Cool stuff - thanks for sharing! Yes the second demo is a good example of the type of animation we need. I would be interested in seeing how you achieve reverse in this case (assuming you support that) since this particular example implies workflow in a single path. Also how do you control the life cycle of the cloned elements i.e. destroy vs hide?

 

Your approach seems good to me, and since it works with third party framework hooks, it should work in popular ones like react, angular and vue. Is this something you can share here or is it proprietary code? I was about to go down this path but it might save some time to piggy back what you already have :)

 

I would love to see something like this built as a plugin or high level service in gsap (hint hint @Jack lol). As you said Blake, there really isn't anything out there that allows UI developers to make these kinds of interactive applications easily. This would be a real game changer for application developers and gsap seems like a perfect fit here. I think this would lure developers in droves to gsap if something like this was available. Animation developers can continue to work with the core components but the rest of us could leverage these high level wrappers. For the most part, I find myself always trying to do the same sort of transitions i.e. fade in/out, slide, push etc... and rarely do complex artistic animations that I see from the many talented artists on this site. I still however, need the performance, features and ease of use of gsap.

  • Like 3
Link to comment
Share on other sites

Unfortunately, I don't have any code to share. The project was something I was hoping to release, but I was stupid and kept everything local, losing everything in hard drive crash. And around that time, Angular came out with Material Design and a way to do similar animations, so I didn't see much need for it anymore.

 

But I remember how I did most of it. For the clones, I would remove after them once the animation was complete. I didn't set it up for reversible animations because the element may change into something else, like in that second Polymer demo. So there's no guarantee that a reverse would ever happen, or that on reverse, the styling/position of the element would be the same. Lots of stuff moves around, and changes shapes for these animations.

 

After looking back at it, I can see that people still need a way to do complicated animations without much thought. It's been several years since Material Design came out, and they still haven't explained how all those fancy animations work. I had to look at a bunch of slow motion videos to figure out what's going on because they happen so fast.

 

 

.

  • Like 1
Link to comment
Share on other sites

  • 4 weeks later...

Hey Blake,

 

Have you seen this library? https://github.com/Rich-Harris/ramjet

 

I was able to get this working for morphing elements. It basically clones the nodes as well but morphs to another hidden element (and then shows it). I used ramjet just for the morph and then used gsap for the remaining animations. What I thought was nice with this approach is you don't have to track state or position because its already in the document workflow.

 

Unfortunately, I still can't do bezier curves like we were doing above because it just supports an ease function :/ It would be nice if this approach was adopted by gsap and made into a plugin with the appropriate hooks so we could control the morph with gsap as well ;) 

  • Like 1
Link to comment
Share on other sites

Yeah, I saw that library a couple months ago. Rich Harris makes some pretty cool stuff!

 

I actually proposed a similar plugin a couple of years ago...

https://greensock.com/forums/topic/11374-override-properties-when-tweening-classname/?p=46364

 

Here's the demo I made to show off how it would work.

 

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

 

At the time I didn't think about a motion path, but that would definitely be pretty nice if there was a plugin that could do it all.

 

.

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