7.4 Floating Action Button Animation

Watch the video course

Overview

What you'll learn:

  • Review of animation
  • variants properties

Final Result

FAB animation result

See for yourself! FAB.

FAB Animation

In this and the next post, we’ll build this Floating action button with icons from Material Design.

As a review of the techniques we’ve learned so far, we’ll also build the staggering animations from scratch.

Since we'll be reviewing past material, I’ll be moving at a faster pace! As a side note, FAB stands for floating action button!

Let's take a look at our stater code.

We have an array of objects here for our smaller buttons.

function App() {
  const buttons = [
    {
      image:
        'https://tinyfac.es/data/avatars/A7299C8E-CEFC-47D9-939A-3C8CA0EA4D13-200w.jpeg',
    },
    {
      image:
        'https://tinyfac.es/data/avatars/AEF44435-B547-4B84-A2AE-887DFAEE6DDF-200w.jpeg',
    },
    {
      image:
        'https://tinyfac.es/data/avatars/2DDDE973-40EC-4004-ABC0-73FD4CD6D042-200w.jpeg',
    },
    {
      image:
        'https://tinyfac.es/data/avatars/852EC6E1-347C-4187-9D42-DF264CCF17BF-200w.jpeg',
    },
  ]
  return <Frame />
}

We'll begin by assuming that we have a Fab component.

function App() {
  const buttons = [...]
return <Fab buttons={buttons} />
}

To give our <Fab> tag flexibility, we'll add and move the button to the center, we'll add the center attribute to <Fab>.

function App() {
  const buttons = [...]
return <Fab buttons={buttons} center />
}

If you remember, we can't just use the center attribute, we also have to support it in our Fab component.

Fab Component

Let's create Fab!

We are going to use object destructuring to extract the properties we want.

function Fab({ buttons, ...props }) {
  return <Frame {...props}>{/* FAB */}</Frame>
}

This <Frame> is just a container. Therefore, to add a clickable button we'll create another <Frame>.

function Fab({buttons, ...props}) {
  return (
    <Frame {...props}>
      {/* FAB */}
<Frame size={60} borderRadius="50%" backgroundColor="white" shadow="1px 1px 5px rgba(0,0,0,0.5)" />
</Frame>
)
}
circle

We can get rid of the blue square and have it fit the size of the circle as follows:

function Fab({buttons, ...props}) {
  return (
<Frame size="auto" background={null} {...props}>
... </Frame> ) }
centered circle

useCycle

Our button should have two states: folded and expanded.

We’ll use useCycle to toggle between the values.

import "./styles.css"

function Fab({buttons, ...props}) {
const [mode, cycleMode] = useCycle("folded", "expanded") return ( ...
)
}

We’ll set mode to the animate attribute of the root <Frame> so that animate will propagate to all its children.

function Fab({buttons, ...props}) {
  const [mode, cycleMode] = useCycle("folded", "expanded")
  return (
<Frame size="auto" background={null} animate={mode} {...props}>
... </Frame> ) }

onTap

For the actual button, we'll add the onTap function and call cycleMode.

function Fab({buttons, ...props}) {
  const [mode, cycleMode] = useCycle("folded", "expanded")
  return (
<Frame size="auto" background={null} animate={mode} {...props}>
{/* FAB */} <Frame size={60} borderRadius="50%" backgroundColor="white" shadow="1px 1px 5px rgba(0,0,0,0.5)" onTap={function(){ cycleMode() }} /> </Frame> ) }

variants

We'll now add variants for our folded and expanded modes to display different image icons.

<Frame
  size={60}
  borderRadius="50%"
  backgroundColor="white"
  shadow="1px 1px 5px rgba(0,0,0,0.5)"
variants]{{
folded:{},
expanded:{}
}}
onTap={function(){ cycleMode() }} />

Because <Frame> has the image prop, we can use it inside variants to change images according to the state.

SVG Icons

We don't have to use a url path because we have the images downloaded inside our project's public directory.

If you don't have those images, here are the links: share.svg and x.svg.

<Frame
  size={60}
  borderRadius="50%"
  backgroundColor="white"
  shadow="1px 1px 5px rgba(0,0,0,0.5)"
  variants={{
    folded: { image: "/share.svg" },
    expanded: { image: "/x.svg" }
  }}
  onTap={function(){
    cycleMode()
  }}
/>
icons

The icons are a bit too big. To not affect the size of the button, we can add another <Frame> inside and move the variants attribute inside of the new <Frame>.

{
  /* FAB */
}
;<Frame
  size={60}
  borderRadius="50%"
  backgroundColor="white"
  shadow="1px 1px 5px rgba(0,0,0,0.5)"
  onTap={function () {
    cycleMode()
  }}
>
  <Frame
    size={30}
    background={null}
    center
    variants={{
      folded: { image: '/share.svg' },
      expanded: { image: '/x.svg' },
    }}
  />
</Frame>

Note that the button was split into 2 Frames to make the new <Frame> a child of the larger one.

smaller icons

Icon animation

Let's add some motion to it.

<Frame
  size={30}
  background={null}
  center
  variants={{
    folded: { image: "/share.svg", rotate:0 },
    expanded: { image: "/x.svg", rotate:180 }
  }}
/>
rotating button

However, if we look closely, it seems like the icons are slightly off-center.

We can fix this by adding x and y attributes to the variants properties.

<Frame
  size={30}
  background={null}
  center
  variants={{
    folded: { image: '/share.svg', rotate: 0, x: -2, y: 1 },
    expanded: { image: '/x.svg', rotate: 180, x: 0, y: 0 },
  }}
/>

Although this means the icons are also being animated horizontally and vertically, those animations are being masked by the rotation.

rotating button

Conclusion

Building this button from scratch was a good review of some of the earlier topics we covered. Do you remember why we use variants? What is useCycle?

In the next post, we’ll add the smaller icons when the button is expanded.