Static prototypes suck. With static prototypes, you can visualize the app’s visual design, but not the interaction design.
Considering how important interaction design is to apps, static prototypes are like a puzzle with pieces missing.
So why doesn’t everyone create interactive prototypes instead? Well, it can take a lot of time to prototype user interactions using a tool like After Effects. It’s hard to spend so much time when you might throw it away the next day.
Enter Framer: an easy-to-use tool for developers and designers to create interactive prototypes. Framer makes it quick to prototype interactions, iterate on the fly, and bring back the magic!
In this Framer tutorial, you’ll recreate this lovely navigation header animation created by Voleg:
In this tutorial, you will focus on prototyping the animation for the menu expanding/collapsing, as that’s the most interesting part. Let’s get started!
Getting Started
First, download and install the following software (you can use the free trials for the purposes of this tutorial):
- Framer: Version 68+ http://framerjs.com/
- Sketch: Version 39.1+ http://www.sketchapp.com/
Open Framer, and you’ll see the following welcome screen:
Click the Animate example project to get a feel for the IDE:
On the left is the Coding Panel; on the right is the Prototype Panel. The Layers Panel sits in the middle.
Feel free to look through the code to get a preview of what’s coming, but don’t worry if you don’t understand it for now. Close this example project, you’re going to create a new one.
Creating a New Prototype
Create a new file in Framer, by going to File\New. Then, click the Insert\Layer to create a new Layer.
Click in a blank spot in the code editor to unselect the layer attributes. You should then see your new layer in each panel:
- As code in the Coding Panel
- As a reference in the Layers Panel
- As a grey square in the Prototype Panel
Mouse over the name of the layer in the Layers Panel to see its location on the prototype.
Change the name to square in the coding panel.
Click on the square next to the left of the line of code to see and modify the layer’s attributes in the Layers Panel and to move it around in the Prototype Panel.
Drag the square to the middle of the prototype, and observe the changes in the Coding and Layers Panels. It should now look something like this:
The changes you make on the layer by interacting with it on the prototype are immediately reflected in the code — and vice versa.
Being able to use either the code or the visual editor to make our changes is a huge advantage of prototyping with Framer as opposed to working with Xcode and Swift.
Delete the existing code, paste the following in the coding panel, and observe the immediate change in the prototype:
square = new Layer x: 250 y: 542 height: 250 width: 250 backgroundColor: "rgba(255,25,31,0.8)" |
Pretty neat, right?
Note: You write code in Framer using CoffeeScript, a simple language that compiles into Javascript. If you’ve never used it before, don’t worry – it’s pretty simple and you can learn a lot about its syntax just by following along with this tutorial.
Note that indentation matters in CoffeeScript, so make sure your indentation matches mine or the code won’t work! Indentation is CoffeeScript’s replacement for {}
‘s.
Tabs vs spaces matter too. Framer defaults to use tabs by default, so if you see code that uses spaces like this:
Backspace until you hit the left edge, and replace spaces with tabs to get something like this:
When you’re copying and pasting code and moving to a newline, always backspace to the beginning of the line. Otherwise your code might be interpreted as part of something else.
Your First Framer Animation
Time to make some magic happen! You’ll make a red square turn into an orange circle when it’s tapped.
Add an empty line after your layer definition, then add the following:
square.onTap -> square.backgroundColor = "rgba(255,120,0,0.8)" square.borderRadius = 150 |
This makes the shape respond to tap events.
Click on the red square, and it will turn into an orange circle.
The biggest advantage of prototyping with Framer, rather than Xcode and Swift, is the ability to interact with your prototype immediately after you make your change. Removing the time-suck of constantly building and running Xcode projects greatly improves your prototyping speed — and your ability to iterate quickly through your designs.
All right, I know what you’re thinking. Snoozeville! The transition is too sudden, and the user can’t return to the red square. That’s easy to fix.
Instead of specifying what happens to the square
layer on a tap, create a new state
.
Replace the code you just added with the following:
# 1 square.states.add orangeCircle: backgroundColor: "rgba(255,120,0,0.8)" borderRadius: 150 # 2 square.onTap -> square.states.next() |
Let’s review this section by section:
- This defines a new state
orangeCircle
forsquare
. This new state sets the layer’sbackgroundColor
to orange and itsborderRadius
to 150. You can see a full list of properties you can set on layer’s in the framer.js documentation. - This configures the tap event so that
square
will transition to its next state.
Click the square to see how the transition has improved:
Your shape now animates to the orangeCircle
state and animates back to the square’s original state. That’s because you’ve added states to a layer loop, so the state after orangeCircle
is the default state.
Let’s try adding another state. Add this line right below section 1:
square.states.add greenRounded: backgroundColor: ("green") borderRadius: 50 |
Now, tap the square in the prototype panel and you’ll see it loop between all three states:
With just a few lines of code, and barely any setup, you’ve created a slick animation!
Think you’re ready for something harder and way cooler, like an actual UI?
Using Framer to Recreate an Animation
Take another look at the animation you’ll be recreating:
Again, you’ll be focusing on the menu expanding/collapsing portion of this animation in this tutorial.
The most important part in recreating an animation is to break it down to its basic components. Not only does this help you understand the animation better, but it also helps you create the step-by-step instructions on how to do it yourself!
There are three different problems to tackle in this animation: The selected state, the deselected state, and the transition between them.
Deselected
This is what the user sees initially:
- There are four tappable banners.
- Each banner has a different color, icon, and title.
- Each banner casts a shadow on the banner below it.
Selected
This is what the user sees when they tap a banner:
- The top bar with the main color, title and a smaller icon
- A white background
- The top bar casts a shadow on the layer underneath
- Four cards underneath, two on the top, two on the bottom, and spaced evenly
- The color of the cards depends on the selected banner
The user taps to transition between the two states. In the deselected state, the user taps on one of the colored banners. In the selected state, the user taps on the top bar.
Transitioning from Deselected to Selected
Here’s what happens as the UI transitions from deselected to the selected state:
- All four banners shrink to slightly less than half of their original height.
- The tapped banner goes on top, the others fold behind it.
- The banner animation starts slow, speeds up quite a bit, then slows down dramatically.
- The only remaining shadow is the one on the top banner.
- The icon on the quite suddenly shrinks from 100% of its size to nothing. The animation starts quickly but finishes with a lot of damping.
- The text in the middle of the tapped banner moves vertically to the middle of the top bar.
- The background cards have no real animation, but the colors of the cards match the tapped banner.
Transitioning from Selected to Deselected
Here’s what happens as the UI transitions from the selected to the deselected state:
- All four banners expand and move down, so that one begins where the other ends to cover the whole screen. Each banner is 1/4th of the height of the screen
- The banners are always in a specific order, so the banner you tap on doesn’t influence how the banners appear in the deselected state.
- The expanding banner animation starts off a little slow, speeds up quite significantly, then finishes with a lot of damping.
- Each banner casts its own shadow.
- The icon reappears; it grows from nothing to 100% of its size. The animation starts quickly, then finishes with damping.
- The text moves about 4/5ths of the way down the expanded banner.
Now that the visuals are broken down, you can move on to the fun part — building the animation!
Note: This particular animation wasn’t too difficult to dissect, but it’s usually helpful to record an animation with QuickTime and then slow it down with After Effects or Adobe Photoshop to pick out the finer details. For example, this technique helped me discern whether the icon should disappear immediately when you tap a banner, or if it’s a more gradual disappearing act.
Laying Things Out
First, download the starter assets for this tutorial. This package contains all the assets needed to recreate the animation — the icons, the text and the cards.
First, install the Archive font included in the starter assets. It’s important to do this before you open the Sketch file, otherwise it won’t display correctly.
Next, open SweetStuff.sketch to see the assets I’ve created for you:
Go back to Framer and create a new file with File\New. Then click the Import button on the toolbar:
Leave the export size at @1x and press Import again. You should see something like this:
Framer just created a variable named sketch
that holds references to all the layers in your Sketch file you’ll need to create your prototype. You can see all these layers in the Layers Panel.
Note: Make sure that you have the Sketch file opened before you press Import, or otherwise Framer won’t be able to detect your Sketch file.
By default, the device selected for your prototype is an iPhone 6S Silver. However, your prototype should work on lots of other devices as well. You’ll create a device
variable as a reference to the selected device, and you’ll lay things out relative to the device’s screen.
Add the following after the sketch
variable:
device = Framer.Device.screen |
Color names can be verbose, so define the following color constants at the top to make them friendlier to use:
blue = "rgb(97,213,242)" green = "rgb(150,229,144)" yellow = "rgb(226,203,98)" red = "rgb(231,138,138)" |
Next, create a container layer to hold everything. Add the following:
container = new Layer width: device.width height: device.height backgroundColor: 'rgba(255, 255, 255 1)' borderRadius: 5 clip: true |
This creates a container
layer and sets its size equal to that of the device. Finally, you set the backgroundColor
to white; this will serve as the background color for the cards. You’ll add all subsequent layers to the container layer.
By setting clip
to true
, nothing outside the bounds of the container will show.
Deselected State
Onwards to the fun part! You’ll start by creating the deselected screen first.
Start with the menu layers. Since you know the width and height of the layers, define them as constants as follows:
menuHeight = container.height/4 menuWidth = container.width |
Add the code below and observe what happens:
cookieMenu = new Layer height: menuHeight width: menuWidth x: 0 y: 0 backgroundColor: blue |
This creates a new layer for your cookie menu, gives it the backgroundColor
blue and sets the x
origin, the y
origin, the width
and the height
.
Now you’ll do the same thing for the other menus, but keep in mind that the y
has to start at the end of the previous layer. The y-position of the current layer is y: previousMenu.y + previousMenu.height
:
Add the following code to create the other menus:
cupcakeMenu = new Layer height: menuHeight width: menuWidth x: 0 y: cookieMenu.y + cookieMenu.height backgroundColor: green fruitMenu = new Layer height: menuHeight width: menuWidth x: 0 y: cupcakeMenu.y + cupcakeMenu.height backgroundColor: yellow iceCreamMenu = new Layer height: menuHeight width: menuWidth x: 0 y: fruitMenu.y + fruitMenu.height backgroundColor: red |
Next, add some shadow to create the illusion of the menus being on top of one another.
Add the following to the end of every layer definition:
shadowY: 2 shadowBlur: 40 shadowSpread: 3 shadowColor: "rgba(25,25,25,0.3)" |
Hooray! Shadow! But… it’s going the wrong way. That’s because the layers were added on top of each other from top to bottom, with the ice cream menu on top, rather than the other way around.
Create a function to reposition the menus and bring them on top in the correct order. The signature for a function definition in Framer looks like this:
functionName = ([params]) -> |
Add the following function to reposition the menus after your layer definitions:
repositionMenus = () -> iceCreamMenu.bringToFront() fruitMenu.bringToFront() cupcakeMenu.bringToFront() cookieMenu.bringToFront() repositionMenus() |
The repositionMenus
function takes no parameters and brings the menus layers to the front in the order from bottom to top. At the end, you call the function.
Check out the prototype panel – now the shadows should appear in the proper order.
Adding Icons and Titles
Now let’s add some icons and titles to your menus, starting with the cookie menu.
Add the following code to the end of your file:
cookieIcon = sketch.Cookie cookieIcon.superLayer = cookieMenu cookieText = sketch.CookieText cookieText.superLayer = cookieMenu |
This adds two new variables: cookieIcon
and cookieText
and sets them equal to the corresponding sketch layers, Cookie
and CookieText
. You then set the superLayer
of both to container
.
Your next task is to position these layers now. cookieIcon
should go in the center of its superLayer
, and cookieText
should center horizontally, but position itself 4/5ths of the way down its superLayer
. The icon should go in the center of the layer’s superLayer
.
Add the following code to center the icon:
cookieIcon.center() |
Add the following code to set the position of the text;
cookieText.centerX() cookieText.y = cookieText.superLayer.height * 0.8 |
Now just repeat this for the rest of the menus, using the following code:
cookieIcon = sketch.Cookie cookieIcon.superLayer = cookieMenu cookieIcon.center() cookieText = sketch.CookieText cookieText.superLayer = cookieMenu cookieText.centerX() cookieText.y = cookieText.superLayer.height * 0.8 cupcakeIcon = sketch.Cupcake cupcakeIcon.superLayer = cupcakeMenu cupcakeIcon.center() cupcakeText = sketch.CupcakeText cupcakeText.superLayer = cupcakeMenu cupcakeText.centerX() cupcakeText.y = cupcakeText.superLayer.height * 0.8 fruitIcon = sketch.Raspberry fruitIcon.superLayer = fruitMenu fruitIcon.center() fruitText = sketch.FruitText fruitText.superLayer = fruitMenu fruitText.centerX() fruitText.y = fruitText.superLayer.height * 0.8 iceCreamIcon = sketch.IceCream iceCreamIcon.superLayer = iceCreamMenu iceCreamIcon.center() iceCreamText = sketch.IceCreamText iceCreamText.superLayer = iceCreamMenu iceCreamText.centerX() iceCreamText.y = iceCreamText.superLayer.height * 0.8 |
This should give you something that looks very much like the deselected state:
Whew! You made it. Give yourself a little pat on the pack for getting this far. :]
Refactoring
But, in retrospect, that’s a whole lot of code to make one screen…
Just think of how unwieldy your code might be when you’ll have to juggle all the state changes and other details.
Preposterous! This looks like a good time to refactor your code.
Instead of creating each menu layer and its icon and title separately, you’ll create some helper functions to make the code look neater and easier to read.
Replace everything you’ve done so far after the menuHeight
and menuWidth
definitions with the following:
# 1 menuItems = [] colors = [blue, green, yellow, red] icons = [sketch.Cookie, sketch.Cupcake, sketch. Raspberry, sketch.IceCream] titles = [sketch.CookieText, sketch.CupcakeText, sketch.FruitText, sketch.IceCreamText] # 2 addIcon = (index, sup) -> icon = icons[index] icon.superLayer = sup icon.center() icon.name = "icon" # 3 addTitle = (index, sup) -> title = titles[index] title.superLayer = sup title.centerX() title.y = sup.height - sup.height*0.2 title.name = "title" # 4 for menuColor, i in colors menuItem = new Layer height: menuHeight width: menuWidth x: 0 y: container.height/4 * i shadowY: 2 shadowBlur: 40 shadowSpread: 3 shadowColor: "rgba(25,25,25,0.3)" superLayer: container backgroundColor: menuColor scale: 1.00 menuItems.push(menuItem) addIcon(i, menuItem) addTitle(i, menuItem) repositionMenus = () -> menuItems[3].bringToFront() menuItems[2].bringToFront() menuItems[1].bringToFront() menuItems[0].bringToFront() repositionMenus() |
Let’s review this section by section:
- This sets up some arrays to keep track of the menus, colors, icons and titles.
- This is a helper function to insert the icon sublayer for each menu item.
- This is a helper function to add the title sublayer for each menu item.
- This loops through each menu item, creating a new layer and calling the helper functions to add the icon and title. Note that it stores each layer in a
menuItems
array so you can easily access them later.
And this, ladies and gentleman, is clean code. Onwards to the next challenge!
Transitioning to Selected State
The first step is to add a new state named collapse
to the main menuItems
loop. Think about this for a second though. What do you need to do to menuItem
when it enters the collapse
state?
You’ll need to transition from an expanded state to a collapsed state.
Review the changes from before:
- The y-position of the layer becomes 0.
- The height goes from 1/4th of the screen to about 1/9ths of the screen.
- The icon disappears gradually.
- The y-position of the text changes so the text moves up.
- You only see the shadow of the selected
menuItem
Focus on the easy things first: the height and y-positions for the menuItem
. Comment out the two following lines in the for
loop, but don’t remove them — you’ll need them later.
# addIcon(i, menuItem) # addTitle(i, menuItem) |
Command + /
on the line.Add the following collapsedMenuHeight
constant with the other constants after the container
layer:
collapsedMenuHeight = container.height/9 |
Add the collapse
state right before menuItems.push(menuItem)
, just after the commented out parts:
menuItem.states.add collapse: height: collapsedMenuHeight y : 0 |
Now to make the menuItems
listen and respond to tap events.
Define the following tap events on each menuItem
, just after the menuItem
for loop and before repositionMenus
:
#onTap listeners menuItems[0].onTap -> for menuItem in menuItems menuItem.states.next() this.bringToFront() menuItems[1].onTap -> for menuItem in menuItems menuItem.states.next() this.bringToFront() menuItems[2].onTap -> for menuItem in menuItems menuItem.states.next() this.bringToFront() menuItems[3].onTap -> for menuItem in menuItems menuItem.states.next() this.bringToFront() |
Every time you tap on a menuItem
, you loop through all the menuItems
and transition each of them to its next state
. this.bringToFront()
brings the tapped menuItem
to the top. By declaring each tap event separately, it’s easier to change them later.
this
is useful when you’re trying to refer to an object you’re manipulating, instead of using its name explicitly. It’s clearer and, in most cases, shorter and it increases the legibility of your code.Give it a try to see how your touch interaction works so far:
Finishing Touches
So far so good, except you need to add the icon and titles back, and fix a few issues.
To do this, you’ll need to track when a menuItem
is selected.
Add the following variable after the menuItems
for loop and before the onTap
listeners:
selected = false |
You initially set selected
to false
since nothing is selected when you start.
Now you’re ready to start writing the function for switching between selected and deselected states. Add the following code before the repositionMenus
function, after the onTap
listeners:
# 1 menuStateChange = (currentItem) -> # 2 for menuItem in menuItems menuItem.states.next() # 3 if !selected currentItem.bringToFront() # 4 else repositionMenus() # 5 selected = !selected |
This function does the following:
- Accepts a parameter
currentItem
, which is the tappedmenuItem
. - Iterates through all
menuItems
and makes each transition to its next state. - If no
menuItem
was selected, thenselected
isfalse
, so you placecurrentItem
at the front. - If a
menuItem
was selected, thenselected
istrue
, so you return the menus to their default states withrepositionMenus()
. - Finally, you flip the state of the
selected
Boolean.
Now you can leverage this function in your onTap
implementation. Change the onTap
implementation for each as shown below for each menuItem
instance, 0 through 3:
menuItems[0].onTap -> menuStateChange(this) |
Awesome. Now if you look closely, you may notice that when the menu items are compressed, the shadow looks particularly heavy. This is because all four layers have shadows that are stacking on top of each other.
To fix this, in menuStateChange
, change the for
loop as follows:
for menuItem in menuItems if menuItem isnt currentItem menuItem.shadowY = 0 menuItem.shadowSpread = 0 menuItem.shadowBlur = 0 menuItem.states.next() |
This hides the shadow for any layer that isn’t the topmost layer, when the layers are collapsed.
Even though the animation looks pretty cool by now, there’s still two key things missing: the icon and the text.
Uncomment the below code found in the menuItems for
loop (make sure that these are the last lines in the for loop):
addIcon(i, menuItem) addTitle(i, menuItem) |
Remember when you added names to the icon and title sublayers in addIcon
and addTitle
? Here’s where those come in handy. Those names will help you distinguish between different sublayers
in a menuItem
.
Add the following function to collapse the menu, just after menuStateChange()
:
collapse = (currentItem) -> # 1 for menuItem in menuItems # 2 for sublayer in menuItem.subLayers # 3 if sublayer.name is "icon" sublayer.animate properties: scale: 0 opacity: 0 time: 0.3 # 4 if sublayer.name is "title" sublayer.animate properties: y: collapsedMenuHeight/2 time: 0.3 |
Here’s what’s going on in the code above:
- Iterate through each of the
menuItems
. - For each
menuItem
, iterate through itssubLayers
. - If you hit the
icon
sublayer, animate the scale to 0 and the opacity to 0 over the space of 0.3 seconds. - When you hit the
title
sublayer, animate the y-position to the middle of the current menu.
Now, add the following function immediately below the one you just added:
expand = () -> # 1 for menuItem in menuItems # 2 for sublayer in menuItem.subLayers # 3 if sublayer.name is "icon" sublayer.animate properties: scale: 1 opacity: 1 time: 0.3 # 4 if sublayer.name is "title" sublayer.animate properties: y: menuHeight * 0.8 time: 0.3 |
Taking it step-by-step:
- Iterate through each of the
menuItems
. - For each
menuItem
, iterate through itssubLayers
. - If you hit the
icon
sublayer, animate the scale to 100% and the opacity to 1 over the space of 0.3 seconds. - When you hit the
title
sublayer, animate the y-position tomenuHeight * 0.8
.
Add the calls to collapse()
and expand()
to menuStateChange()
as shown below:
menuStateChange = (currentItem) -> # remove shadow for layers not in front for menuItem in menuItems if menuItem isnt currentItem menuItem.shadowY = 0 menuItem.shadowSpread = 0 menuItem.shadowBlur = 0 menuItem.states.next() if !selected currentItem.bringToFront() collapse(currentItem) else expand() repositionMenus() selected = !selected |
Check out the prototype panel and now you’ll see the icon and title animate properly as well:
You’re almost at the finish line! You don’t have much farther to go! :]
Animation Settings
You can animate every layer in Framer; you can fine tune the animation by setting the following values:
- properties:
width
,height
,scale
,borderRadius
and others; the layer will animate from whatever state it is in to what you define here. - time: How long the animation lasts.
- delay: If there should be a delay before the animation, and how long the delay should be.
- repeat: How many times to repeat the animation.
- curve: How fast the animation should run.
- curve options: Fine-tuning options for the animation curve.
The most confusing two above are the curve and the curve options setting. You can use this tool to prototype animation curves with options: Framer.js Animation Playground.
Since you haven’t defined an animation curve for any of the animations, the prototype uses Framer’s default curve, ease:
This curve makes the prototype look stiff and unnatural. The curve you want is a spring curve that lets you have more control over what happens at each step of the transformation.
Time to translate the words used to describe these animations into numbers through the curveOptions settings.
This animation calls for a simple spring curve with the following parameters:
- tension: How “stiff” the curve should be. The bigger the number, the more speed and bounce the animation should have.
- friction: How much resistance to apply. The bigger the number, the more damped the animation will be.
- velocity: The initial velocity of the animation.
Often you won’t just know what these numbers are. You will have to play around until you’re satisfied.
There are two animations with different speeds going on here:
Animating the menus: Starts slow, speeds up a lot, then slows down dramatically:
Animating the icon and title: The icon size goes from 100% to 0%, and the animation is fairly sudden. It starts fast, but finishes with a lot of damping:
Compared to the previous animation, this one has a lot less tension and the transitions between the different speeds are quicker. Give it a similar friction number for that nice damping effect and a small initial velocity.
Add the following just before menuItems.push(menuItem)
:
menuItem.states.animationOptions = curve: "spring" curveOptions: tension: 200 friction: 25 velocity: 8 time: 0.25 |
This sets a spring animation for the menus and sets tension to 200, friction to 25, and velocity to 8. It runs just a little faster than the icon and title animations.
Find each instance of sublayer.animate
, and add the following under the time
line in the properties
section, matching the indentation:
curve: "spring" curveOptions: tension: 120 friction: 18 velocity: 5 |
This will add a similar spring animation to the titles and icons, but they will be a little less springy and move a little slower than the menu sections.
You’ll add this code in four times in total: twice under collapse
and twice under expand
for both the icon and the title subLayers.
To compare your results, here’s an example of the collapse
function with the required indentation:
collapse = (currentItem) -> for menuItem in menuItems for sublayer in menuItem.subLayers if sublayer.name is "icon" sublayer.animate properties: scale: 0 opacity: 0 time: 0.3 curve: "spring" curveOptions: tension: 120 friction: 18 velocity: 5 if sublayer.name is "title" sublayer.animate properties: y: collapsedMenuHeight/2 time: 0.3 curve: "spring" curveOptions: tension: 120 friction: 18 velocity: 5 |
Here’s how things should look after you’re done:
Where to Go From Here?
And that’s a wrap! Congrats on your first Framer prototype :]
You can download the finished project here. It goes a bit further than this tutorial in that it shows you how to display the cards inside each menu item as well. The complete project, along with this same prototype created in Xcode + Swift are included.
- To learn more about Framer, check out their documentation and learning resources, and their medium articles and tutorials.
- Framer layers can respond to many other events such as panning, swiping and pinching. To learn more about these events, check out the Framer event documentation at: http://framerjs.com/learn/basics/events/.
- To learn more about what you can do with states, check out http://framerjs.com/learn/basics/states/.
- To learn more about curves are and how they’re used, take a look at Framer’s Basic Animation under the curves section.
- To learn more about animation curves, and other advanced animation techniques in Framer, have a read through their Animation Documentation.
- You can also check out UI Movement for some interactive UI inspiration.
- Finally, you can learn more about animation with Swift and Xcode with our book iOS Animations by Tutorials.
If you have any questions or comments on this tutorial, please feel free to join the discussion in the forums below!
The post Using Framer to Prototype iOS Animations appeared first on Ray Wenderlich.