Before starting the animations, we’ll move the ScrollComponent down while moving its content up. We do it instantly, without animation.

Okay, step by step:

# Make the ScrollComponent jump to its content’s position

scroll_now_playing.y = scroll_now_playing.content.y + 33

(Note that we’re not using scrollY here, but the content layer’s y position, which increases when scrolling down.)

So no matter how far the user has scrolled down, our ScrollComponent will always end up in the correct place.

Now we move the content back to the top:

# … and reset the content to its initial position

scroll_now_playing.scrollToPoint

y: 0

no

The scrollToPoint() function does what it says: it lets you scroll to a certain point. By setting its ‘animate’ argument to no , this will happen instantly, without animation.

All together it should look like this:

scroll_now_playing.onScrollEnd ->



if scroll_now_playing.scrollY < -88 # 121 points minus 33



# Make the ScrollComponent jump to its content’s position

scroll_now_playing.y = scroll_now_playing.content.y + 33



# … and reset the content to its initial position

scroll_now_playing.scrollToPoint

y: 0

no

Please try it. You can scroll up and down all you want, but once you pull down far enough, it will stay at the spot where you released it.

Scrolling stops once scrolled down more than 88 points

Now get ready. We’ll have nine animations, with different timings, all run at the same time.

First set of animations

The first set of six animations starts immediately, and the duration for all of them will be a third of a second.

# -- First set of animations, over a third of a second -- #

firstSetDuration = 0.3

In 0.3 seconds we’ll:

Show the mini-player ( opacity )

) Hide the transparent gray overlay behind it ( opacity )

) Reset the “Library” screen in the back ( scaleX , y , borderRadius ) …

, , ) … … and do the same for the “For You” screen

Make the Status Bar black again ( invert )

) And move the Tab Bar up ( y )

Here we go.

Showing the mini-player: We make it visible again and animate its opacity back to 1 .

Mini_Player.visible = yes Mini_Player.animate

opacity: 1

options:

time: firstSetDuration

Hide the transparent gray overlay by animating its opacity to zero.

overlay.animate

opacity: 0

options:

time: firstSetDuration

Next, we move scroll_library and scroll_for_you back to the top of the screen, reset their horizontal scale, and remove their border radius.

scroll_library.animate

scaleX: 1

y: 0

borderRadius: 0

options:

time: firstSetDuration



scroll_for_you.animate

scaleX: 1

y: 0

borderRadius: 0

options:

time: firstSetDuration

(Initially, we only changed scroll_library , but after use of the prototype either one of them might be in the background.)

Earlier we made the Status Bar white by changing its invert ; we now dial it back to the default value: 0 .

$.Status_Bar.animate

invert: 0

options:

time: firstSetDuration

By setting the Tab Bar’s bottom, maxY , to the bottom of the screen, it will slide back up.

$.Tabs.animate

maxY: Screen.height

options:

time: firstSetDuration

Because we used a variable, firstSetDuration , to set the duration for these animations, we can slow all of them down to better observe what’s happening.

Like having them animate with a duration of 3 seconds …

# -- First set of animations, over a third of a second -- #

firstSetDuration = 0.3 * 10

… as I did for the GIF below:

Second set of animations

The next two animations also start immediately but are slower, and they have a subtle bounce.

# -- Second set of animations: 0.7 seconds -- #

secondSetDuration = 0.7

In 0.7 seconds we’ll:

Move the whole “Now Playing’ screen (which includes the mini-player) downwards ( y , borderRadius )

, ) Make the album cover fit on the mini-player (a state animation)

We don’t want to animate everything off-screen because the mini-player should still be visible, so we move the top of the “Now Playing” screen to the height of the Tab Bar + the height of the mini-player.

scroll_now_playing.animate

y: Screen.height - $.Tabs.height - Mini_Player.height + 1

borderRadius:

topLeft: 0

topRight: 0

options:

time: secondSetDuration

curve: Spring(damping: 0.77)

(Apparently, we need to add 1 extra point to not have a small gap appear.)

We also get rid of the border radius because otherwise, we would have a mini-player with rounded corners.

The added Spring curve is only slightly bouncy, with a 0.77 damping instead of the default 0.5 .

And we use this same spring curve when shrinking $.Album_Cover to its "mini" state:

$.Album_Cover.animate "mini",

time: secondSetDuration

curve: Spring(damping: 0.77)

We did not include animation options when creating "mini" (as we did for the "playing" and "paused" states), but we can add the desired duration and curve here.

Here’s a GIF of all eight animations at a tenth of their speed:

Last animation: Hiding the “Now Playing” screen

This last animation starts 0.5 seconds later because we want to be sure that the mini-player is in place before fading out the screen underneath it.

$.Now_Playing.animate

opacity: 0

options:

delay: 0.5

time: 0.5

(Here we’re animating the opacity of the $.Now_Playing Sketch layer that’s inside our ScrollComponent.)

Background Blur

Now that you can see the mini-player’s transparency you’ll notice something is missing: Background Blur. Whatever is underneath the mini-player should be blurred.

Go back to Design, select the mini-player, give it a Blur of 25 , and then change this blur from Layer to Background.

Here’s the result:

Ah, and now that we switched to the mini-player we can also “flip the switch”:

# The mini-player is now active

miniPlayerActive = yes

21. Transitioning from the mini-player back to “Now Playing”

Now we want to get back. When the user taps the mini-player, it should sprout into the “Now Playing” screen.

We’ll listen for an onTap on the mini-player’s background layer.

Mini_Player_Background.onTap ->

Why the background? Because this way we can continue to use the “Play” and “Pause” buttons on the mini-player without also triggering this transition.

In the event handler, we start by making the “Now Playing” screen visible:

# Show the “Now Playing” screen, so that it doesn’t have to fade in

$.Now_Playing.opacity = 1

It’s underneath the mini-player anyway, and we don’t want to animate its transparency while sliding it up.

First set of animations

Now, our animations. There’s a fast set (one-third of a second) and a slower set (half a second). First the fast set:

# -- First set of animations, over a third of a second -- #

firstSetDuration = 0.3

We hide the mini-player …

# Fade out the mini-player

Mini_Player.animate

opacity: 0

options:

time: firstSetDuration

… and in the same 0.3 seconds we lower the Tab Bar:

# Lower the Tab Bar

$.Tabs.animate

y: Screen.height

options:

time: firstSetDuration

It’s a small movement anyway (compared to the whole “Now Playing” screen sliding up).

Here’s a GIF of what’s happening (also at one-tenth of the speed):

Second set of animations

The second set starts at the same time, but these animations run slower: 0.5 seconds. Just like the first set, they use the default Bezier.ease curve.

# -- Second set of animations: half a second -- #

secondSetDuration = 0.5

The most obvious animation is the “Now Playing” screen moving back up:

# Animate the ScrollComponent upwards

scroll_now_playing.animate

y: 33

borderRadius:

topLeft: 10

topRight: 10

options:

time: secondSetDuration

(We also restore the border radius on the top corners.)

And at the same time, we want to bring the album cover back to its bigger size. But, we should check if the music is playing, so that we can animate it to the correct state.

As you know, the player object on the audio player gives us access to its HTML5 “audio” element. One of this element’s properties is paused , which will be ‘true’ when the music isn’t playing.

if audio.player.paused

$.Album_Cover.animate "paused",

time: secondSetDuration

else

$.Album_Cover.animate "playing",

time: secondSetDuration

curve: Bezier.ease

By giving these animations a time we override the durations that were set when creating the states.

We also don’t want the spring curve contained in "playing" , so we override it with a Bezier.ease curve.

What remains are the things that happen in the background, underneath the “Now Playing” screen:

The transparent gray overlay should fade in again

The screen in the back should also become a card

The Status Bar should be white again

The gray overlay:

# Show the transparent gray overlay

overlay.animate

opacity: 1

options:

time: secondSetDuration

Dealing with the “Library” and “For You” screens:

# Shrink & move the screens in the back

scroll_library.animate

scaleX: 0.93

y: 20

borderRadius: 10

options:

time: secondSetDuration



scroll_for_you.animate

scaleX: 0.93

y: 20

borderRadius: 10

options:

time: secondSetDuration

(Again, only one of them will be visible at this point.)

The Status Bar:

# Make the Status Bar white

$.Status_Bar.animate

invert: 100

options:

time: secondSetDuration

All set. We should now make the mini-player untappable so that it can’t be triggered inadvertently when the user swipes down…

Mini_Player.visible = no

… and we register that the mini-player is not active.

miniPlayerActive = no

Prevent album cover animations when the mini-player is active

By now you might be asking why we even need this miniPlayerActive variable.

Well, tap the “Play” button in the mini-player.

The album art grows and shrinks as instructed

These animations should not happen when we’re in the mini-player.

Go back to the # Animating the album cover fold.

Until now the onplaying() and onpause() functions looked like this:

# When the music started playing

audio.player.onplaying = ->

$.Album_Cover.animate "playing"

# Show and hide the small buttons

Mini_Button_Play.visible = no

Mini_Button_Pause.visible = yes

# … and also the big buttons

$.Button_Play.visible = no

$.Button_Pause.visible = yes

(The onpause() function will contain similar code.)

With an extra if line we’ll check for miniPlayerActive , and only when it’s not active ( no ) we change the state of $.Album_Cover .

# When the music started playing

audio.player.onplaying = ->

if miniPlayerActive is no

$.Album_Cover.animate "playing"

# Show and hide the small buttons

Mini_Button_Play.visible = no

Mini_Button_Pause.visible = yes

# … and also the big buttons

$.Button_Play.visible = no

$.Button_Pause.visible = yes

(Add the same line to the onpause() function.)

Done!

I hope you 👏 liked this tutorial.

If you did, check out my book. It has similar tutorials for two more apps, and a whole lot more about Framer Code. And there’s a free preview version!