7.5 FAB Part 2: staggerChildren

Watch the video course

Overview

What you'll learn:

  • staggerChildren
  • staggerDirection
  • initial

FAB Part 2

Now, we'll finish our floating action button.

First, let's add these smaller buttons when the FAB is expanded.

fab

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>
... ) }
overlapping buttons

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>
relative positioning

Adding images

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>
populated buttons

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"
/>
centered buttons

Styling our buttons

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)"
/>
border and shadow added

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}
  ...
/>

Animation

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 }
  }}
/>
scaled buttons

Stagger Effect

Now it’s time to add some stagger effect.

We can check out Framer's documentation to stagger our animation.

staggerChildren

stagger documentation

The property is called staggerChildren and, in the example on the right, we see that staggerChildren is set in the transition property.

stagger example

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}}
}} > ... ) }
stagger animation

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.

Expanding stagger

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}}
}} > ... ) }
stagger both states

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> ... ) }
fab fixed

We certainly aren't done yet!

staggerDirection

When 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}}
}} >
stagger direction fixed

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.

Flashing image in the next sections

<Frame
  background={null}
  size="auto" center="x"
  position="relative"
  variants={{
folded: {transition:{staggerChildren:0.05}},
expanded: {transition:{staggerChildren:0.05, staggerDirection:-1}}
}} >
final result

Launching bug

Lastly, when the app launches, we can see the small buttons appear shortly.

initial refresh bug

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>
)

Final Result

initial problem fixed, final result

Conclusion

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!