Animation, Framer, Prototype
Creating a Button Loading Animation

Creating a Button Loading Animation

When you click or tap on a button, it’s always nice to have some sort of visual feedback to let you know that the app or website received your request. Even better – if you show the progress so the user has a sense of how much time is left.

In this Framer tutorial, we’ll explore prototyping a button loading animation. Here’s what we’re going to build:


While the animation looks simple, it touches on a lot of useful concepts including animation chaining ( following one animation after another), the “change” event, and a neat trick on how to animate curves in Framer.

To start, update your viewer to Fullscreen by clicking the Viewer button at the top right of the toolbar.

Set a background color by creating a backgroundLayer and setting the backgroundColor property. A background layer is just a special layer type that is the width and height of the screen. Since we won’t be messing with the background in the prototype, we don’t need to set it to a variable.

We’re also going to set the default animation properties. By default, Framer uses a default time of 1 second and an “ease-in-out” curve. We’ll make the default time .5 seconds and an “ease-in” curve. To set the default animation properties, use Framer.Defaults.Animation.

We’ll create a new layer for the button and define its properties, including its width, height, background color, and border radius. We’ll also give it a slight shadow. When you set your layer to a variable, you can call it almost anything you want but you should try and use something descriptive that would be understandable to someone who is trying to understand your code. In our case, we’ll call our layer variable “button”.

For the purposes of this prototype, we’ll just center the button in the middle of the screen. We can do this by calling the center function.

Our animation is going to turn this button to a circle and then back to a rectangle. We’ll need to remember the button’s x position, so we’ll create a variable that we’ll name buttonOriginX that will hold the current position and use it later.

Next, let’s give this button a label. We’ll create another layer, with the same width and height, and set the button to be the superLayer of the label. We’ll also add some text to the layer by setting the HTML property.

We don’t actually see any text in our button yet because the default text color is white, the same color as the button. To fix this, let’s add some styling. With Framer you can set a layer’s style using CSS style properties. To access, for example, the font size property, you can either write[“font-size”] or use camelCase naming – so

We’ll set a few different properties so the text is larger, uses a different font, and more. To center the text vertically, make the line height the same height as the button. To center horizontally, we just set the text align style property to “center”.

Since the textLabel is a subLayer of the button layer, the textLabel’s position is relative to the button instead of the screen. We can confirm this by using Framer’s layer inspector on the textLabel to see the x and y position of the textLayer is 0.


By having the parent/child relationship (superLayer/subLayer), updates to the superLayer are reflected in the subLayer – for example, if you updated the scale of the button, the scale of the label would update as well.

At this point, we should have a styled button in the center of the screen.


When the button is clicked, we’ll want a circle to rotate around the button. In Framer it’s easy to animate a layer to go from point A to point B in a straight line. Animating with curves is a little tricky but there’s a simple way to do it.

We’ll create a hidden layer that will contain the layer we want to animate. I came up with a width of 30 and height of 230 after some trial and error, but you are free to play around with the values to create an animation that works for you. Let’s also center it on our screen. When we have our animation working properly, we’ll hide this layer by setting it’s backgroundColor to null or “transparent”. For now, we’ll show the background so you can see what’s going on once we get to the animation.

Now we’ll create a smaller circle that will be the subLayer of the hidden layer we just created by setting the superLayer property. We’ll want to give the smaller circle a width and height equal to the hidden layer’s width, in this case 30. We can make it a circle by setting the border radius to be 50%.

This is what you should be seeing now.


Once the button is pressed, a chain of animation events will happen:

  1. Animate the rectangle to a circle and hide the label
  2. Show the loading circle
  3. Rotate the loading circle around the circle button and display the progress
  4. Hide the loading circle, animate the circle back to a rectangle and show the updated label

Let’s create each of these as Animation objects. To create an animation object, set a variable equal to new Animation, specify the layer that will be animated, and then the properties and any other options, such as time or the animation curve. To hide the label, we’ll set the opacity property to 0. We’ll also give it a shorter animation time than the default.

To animate the rectangle to a circle, we’ll need to update the width to match the height. We’ll also need to set the borderRadius property to be half of the new width or height, in this case 75px. Since we want the circle to stay in the center of the screen, we’ll need to move the x position. We use the saved x position plus half the difference between the rectangle button width (500) and the circle button width (150) which comes out to 175. Let’s also remove the shadows.

To show the text label, we’ll set opacity to 1.

To show the loading circle, we’ll need to go back up in our code and set its default opacity property to 0.

We can then animate the opacity to 1. We’ll make this animation quick by setting it’s time to 0.1.

To create our circle animation, we’re going to rotate the hiddenLayer 360 degrees by setting the rotation property. Since the loading circle is a subLayer of the hiddenLayer, rotating the hiddenLayer will make it seem like the circle is animating in a full circle. If that doesn’t quite make sense yet, we’ll see how this looks soon.

Once the rotation completes, we’ll want to hide the loading circle.

We’ll then want to turn the circle back to a rectangle. We’ll make it a little wider than the circle and we’ll also remove the border radius so it looks less like a button. As we did with the circle, we’ll need to calculate the new x position based on the original button position plus half the difference of the old and new widths ((500-250)/2 = 125).

Let’s add the logic. We’ll create a click event on the button that will hide the label and animate the rectangle to a circle. To create a click event, we typically use the “on” keyword to start listening to the event, in this case Events.Click. Since this button should only work one time, instead of “on”, we can use “once” to only listen to this event once.

We can chain animations by listening to when the animation from rectangle to circle ends using the event Events.AnimationEnd. When this happens, we want to show the loading circle.

After the loading circle shows, we will rotate it around the circle button.

Go ahead and try out the prototype now so you can see how the hiddenLayer rotating makes the loading circle animate as a curve. You can reload the prototype using command+R or clicking the reload button at the top right or the toolbar. You should see something like this:


Now that you see how we can rotate the hiddenLayer to make the loading circle rotate in a circle, you can go back and hide the background color.

To finish our animation, we’ll create one more animation chain. Once the circle loading ends, lets update the button color to green and change the text label to have a white checkmark, in a larger font size. We’ll also move the label higher to start.

Since we moved the text label higher, we’ll need to add one more animation object to move it back to it’s original position after a slight delay. To give it a little bounce, we’ll also change this animation to a spring curve.

Now all we have to do is to call our last 4 animation objects to hide the loading circle, animate the circle button back to a rectangle, show the updated label, and move it back to its original position.

If you try the prototype now, you’ll notice a bug with the text not being centered.


This is because when we adjusted the button width, the text label width didn’t change. To fix this, we’ll add a “change” event.

With a “change” event, we can listen to when a layer’s property values change and then do something. So we can listen to when the width of the button changes and update the textLabel width to match. To specify the event to listen to, use “change” followed by a colon and then the property to listen to.

Finally, we’ll add one more “change” event – when we detect that we’re rotating our hiddenLayer, we’ll show the textLabel and update it with the loading percentage. Our rotations go from 0 to 360 degrees but we only want our percentage to go from 0 to 100. Instead of doing the math ourselves, we can use the modulate utility, which takes in a value (the current rotation), and the two ranges and returns the value for you.

The value returned isn’t a whole number and has a lot of decimals. We can see this if we run our prototype and print the value on the console.

We’ll need to round the number. We can use another utility function, round, which takes in a number and the number of decimal places to round to. Since we want whole numbers, we’ll use 0 and set the textLabel’s HTML property to our rounded percentage.

To see the percentages, we just need to remember to show the textLabel again, once the toCircle animation ends.

That’s it! Just like that, you have a custom button loading animation that not only let’s someone know your input was received but shows the progress.


You can check out my Button Loading Framer project and the full code here.

Do you want to see more Framer prototype tutorials like this? Leave a comment below or share your thoughts with me on Twitter (@kennycheny). If you’re interested in learning everything to know about Framer, check out my Rapid Prototyping with Framer video course. If you want to know when I post new content, join the mailing list and you’ll be the first to know.

Want more Framer content like this?

Share this Story

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

About Kenny Chen

Hi, I'm Kenny - a UX Designer from Los Angeles who believes that beautiful, subtle movement enhances the user experience and makes products more engaging, dynamic, and memorable. My goal with Prototyping with Framer is to help you design amazing interaction and animation prototypes.