What you'll learn:
staggerChildrenstaggerDirectioninitialNow, we'll finish our floating action button.
First, let's add these smaller buttons when the FAB is expanded.
See for yourself! FAB Animation.
function Fab({ buttons, ...props }) {
const [mode, cycleMode] = useCycle("folded", "expanded")
return (
...
)
}
We’ve got our array of images as a buttons prop. Since we're dealing with an array, we can use the map function to create a list of tags.
function Fab({ buttons, ...props }) {
const [mode, cycleMode] = useCycle("folded", "expanded")
return (
<Frame background={null} size="auto" animate={mode} {...props}>
{buttons.map(button => (
<Frame size={40} borderRadius="50%" />
))}
</Frame>
...
)
}
Our buttons are overlapping because Frames are absolutely positioned by default.
Let’s change our white button and our smaller ones to relative positioning.
<Frame background={null} size="auto" animate={mode} {...props}>
{buttons.map(button => (
<Frame size={40} borderRadius="50%" position="relative"/>
))}
{/* FAB */}
<Frame
size={60}
borderRadius="50%"
position="relative"
backgroundColor="white"
shadow="1px 1px 5px rgba(0,0,0,0.5)"
onTap={function() {
cycleMode()
}}
>
...
</Frame>
To populate our buttons, we'll use the image attribute.
<Frame background={null} size="auto" animate={mode} {...props}>
{buttons.map(button => (
<Frame
size={40}
borderRadius="50%"
position="relative"
image={button.image}
/>
))}
...
</Frame>
We now want to add spacing between the buttons and center them. We'll use the style attribute to add a margin and center to align the buttons. However, if you want, you could also use flexbox on the parent <Frame> to center the buttons.
<Frame
size={40}
borderRadius="50%"
position="relative"
image={button.image}
style={{ marginBottom: 16 }}
center="x"
/>
To make our buttons look nicer, we'll add a border and drop subtle drop shadow.
<Frame
size={40}
borderRadius="50%"
position="relative"
image={button.image}
style={{ marginBottom: 16 }}
center="x"
border="2px solid white"
shadow="1px 1px 5px rgba(0,0,0,0.5)"
/>
Did we miss anything? What should we do if we use the map function?
The key attribute!
Don’t forget to add the key prop to get rid of the warning associated with using map.
<Frame
key={button.image}
...
/>
To animate the small buttons, we can use the variants prop with scale values.
<Frame
key={button.image}
size={40}
borderRadius="50%"
position="relative"
image={button.image}
style={{ marginBottom: 16 }}
center="x"
border="2px solid white"
shadow="1px 1px 5px rgba(0,0,0,0.5)"
variants={{
folded: { scale: 0 },
expanded: { scale: 1 }
}}
/>
Now it’s time to add some stagger effect.
We can check out Framer's documentation to stagger our animation.
staggerChildrenThe property is called staggerChildren and, in the example on the right, we see that staggerChildren is set in the transition property.
staggerChildren holds the duration of the delay.
Note that the staggerChildren option only works when used inside a variant.
To illustrate,
function Fab({ buttons, ...props }) {
const [mode, cycleMode] = useCycle("folded", "expanded")
return (
<Frame
background={null}
size="auto"
animate={mode}
{...props}
transition={{staggerChildren:0.5}}
>
...
)
}
Although we added staggerChildren, there is no delay in the expanded state if you try it out.
To fix this, we'll add variants to this <Frame> and place staggerChildren inside of it.
Make sure transition={{staggerChildren:0.5}} is converted to object notation!
{transition:{staggerChildren:0.5}}
function Fab({ buttons, ...props }) {
const [mode, cycleMode] = useCycle("folded", "expanded")
return (
<Frame
background={null}
size="auto"
animate={mode}
{...props}
variants={{
folded: {transition:{staggerChildren:0.5}}
}}
>
...
)
}
When the mode of the button changes from expanded to folded, this transition is active and there will be a delay for the animations of all the children of this <Frame>.
Since we haven’t defined staggerChildren for the expanded mode, when the mode does change, all the animations still happen at the same time.
Let's add a stagger to the expanded mode.
function Fab({ buttons, ...props }) {
const [mode, cycleMode] = useCycle("folded", "expanded")
return (
<Frame
background={null}
size="auto"
animate={mode}
{...props}
variants={{
folded: {transition:{staggerChildren:0.5}},
expanded: {transition:{staggerChildren:0.5}}
}}
>
...
)
}
However, now the FAB animation is delayed as well because it is a child of the outer <Frame>.
Since we don't want the delay on our FAB, we can move the small buttons and the variants into a separate <Frame>.
function Fab({ buttons, ...props }) {
const [mode, cycleMode] = useCycle("folded", "expanded")
return (
<Frame background={null} size="auto" animate={mode} {...props}>
// new Frame
<Frame
background={null}
size="auto" center="x"
position="relative"
variants={{
folded: {transition:{staggerChildren:0.5}},
expanded: {transition:{staggerChildren:0.5}}
}}
>
{buttons.map(button => (
<Frame
...
/>
))}
</Frame>
...
)
}
We certainly aren't done yet!
staggerDirectionWhen the FAB is expanded, we want the animation to happen in the opposite direction. This is possible with staggerDirection, in which by default is set to 1.
<Frame
background={null}
size="auto" center="x"
position="relative"
variants={{
folded: {transition:{staggerChildren:0.5}},
expanded: {transition:{staggerChildren:0.5, staggerDirection:-1}}
}}
>
Finally, let’s change our delay duration from 0.5 to 0.05. It is too slow for real use but useful during our development phase so that we can see what’s going on.
<Frame
background={null}
size="auto" center="x"
position="relative"
variants={{
folded: {transition:{staggerChildren:0.05}},
expanded: {transition:{staggerChildren:0.05, staggerDirection:-1}}
}}
>
Lastly, when the app launches, we can see the small buttons appear shortly.
We can easily fix this by adding an initial prop to the root frame.
return (
<Frame
background={null}
size="auto"
initial="folded"
animate={mode}
{...props}
>
...
</Frame>
)
Good job making it here!
We did a lot in this post, and we finished module 7!
To recap, we used variants with transition properties of staggerChildren, and staggerDirection. These options make it easy to add a delay to the children of a container <Frame>.
I encourage you to check out other options for controlling the timing of animations at Framer's documentation.
In the next module, we'll create those smooth scrolling effects you see in all modern websites!