What you'll learn:
variants propertiesSee for yourself! FAB.
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 ComponentLet'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>
)
}
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>
)
}
useCycleOur 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>
)
}
onTapFor 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>
)
}
variantsWe'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.
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()
}}
/>
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.
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 }
}}
/>
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.
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.