What you'll learn:
Slider
props
Slider
the wrong wayIn the last post, we passed the animationControls
to the Skinny
component via the cheekAnimate
attribute. For the Slider
, we could just do the same thing. Pass the animationControls
into the room for Slider
.
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider animate={animationControls}/>
</div>
handleDrag
inside Slider
Now we can uncomment onDrag={handleDrag}
in our Slider
component and move our handleDrag
function from App
into our Slider
component.
function Slider() {
function handleDrag(event, info) {
let newScale = transform(info.point.x, [0, 220], [0.4, 1.5])
animationControls.start({
scale: newScale,
transition: { type: "spring", velocity: 0 }
})
}
return (
...
<Frame
...
onDrag={handleDrag}
/>
</Frame>
)
}
This won't work yet because we need to add props
into our Slider
component since animationControls
isn't defined.
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider animate={animationControls}/>
</div>
Although we can't use animationControls
in our handleDrag
function, we can replace it with props.animate
which is technically the same thing.
function Slider(props) {
function handleDrag(event, info) {
let newScale = transform(info.point.x, [0, 220], [0.4, 1.5])
props.animate.start({
scale: newScale,
transition: { type: "spring", velocity: 0 }
})
}
...
}
However, this method is not really good.
If we read and follow this piece of code,
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider animate={animationControls}/>
</div>
it makes sense to pass animate
to Skinny
because it follows the same pattern as <Frame>
. Furthermore, we are animating Skinny's cheek.
In contrast, this approach doesn’t make much sense for Slider
. We don’t want to simply animate Slider
. We want Slider
to take control of the animation for Skinny
.
You might say, we can change the name of animate
to
animationControls
.
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider animationControls={animationControls}/>
</div>
However, it still is not robust and does not feel right. For example, what if we wanted Slider
to control multiple animations? What if we want Slider
to do something else?
Slider
the better wayInstead of passing the animationControls
as a value into Slider
's attributes, we can define onDrag
beside the <Slider>
tag.
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider onDrag={}/>
</div>
handleDrag
back inside App
Then we move back our handleDrag
function from inside our Slider
component back to the App
component. Remember to change props.animate
back to animationControls
.
function App(){
let animationControls = useAnimation()
function handleDrag(event, info) {
let newScale = transform(info.point.x, [0, 220],[0.4, 1.5])
animationControls.start({
scale: newScale,
transition:{type: "spring", velocity: 0}
})
}
...
}
The idea is that we can then pass the handleDrag
function, or any function in the future, into the Slider
component. This is much clearer and flexible than the previous method.
We aren't trying to animate Slider
, so having the animate
attribute with Slider
doesn't make a lot of sense.
By convention, if an attribute is named onDrag
, it is read like "do this when the frame is dragged". Therefore, the onDrag
value is supposed to be a function which means placing animationControls
as onDrag
's value doesn't make much sense.
Our slider can be used for a variety of purposes, such as controlling animation, phone volume, screen brightness, etc because our implementation of handleDrag
is not hard-coded in our Slider
component. The slider can only do what that implementation is bounded to.
We "outsource" the onDrag
behavior with the onDrag
prop so that the consumer/client decide what it will do.
handleDrag
into onDrag
as a prop
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider onDrag={handleDrag}/>
</div>
Then back in our Slider
component,
function Slider() {
return (
...
<Frame
...
onDrag={props.onDrag}
/>
</Frame>
)
}
This way, we can make the slider to do whatever want, and still keep our code tidy.
Why does this work?
The value of this onDrag
attribute is a function since handleDrag
is a function.
<div ...>
<Skinny cheekAnimate={animationControls}/>
<Slider onDrag={handleDrag}/>
</div>
If the handleDrag
function is a machine, we are carrying the machine into the room Slider
, and moving it inside Frame
. Then, Frame
will run this machine when the user drags the slider.
Note that onDrag
in <Slider ... />
does not have to be named onDrag
. We can change it to other names such as onSlide
, as long as we update the Slider
component accordingly.
function Slider() {
return (
...
<Frame
...
onDrag={props.onSlide}
/>
</Frame>
)
}
Alright, just to recap, we broke down a big component into two smaller components, Skinny
and Slider
. We use props
to make components to talk to each other.
The key here is that we have this animationControls
in the App
component, and we pass it into corresponding children, and that’s how they communicate with each other.
Raw Code?
import './styles.css'
function Skinny(props) {
return (
<Frame
width={290}
height={320}
position="relative"
background="transparent"
>
<Frame
background="url(https://cdn.glitch.com/071e5391-90f7-476b-b96c-1f51f7106b0c%2Fskinny-portrait.png)"
width={290}
height={290}
borderRadius={150}
/>
{/* Cheek */}
<Frame
background="url(https://cdn.glitch.com/071e5391-90f7-476b-b96c-1f51f7106b0c%2Fcheek.png)"
width={79}
height={67}
left={155}
top={135}
animate={props.cheekAnimate}
/>
</Frame>
)
}
function Slider(props) {
return (
<Frame
width={280}
height={15}
borderRadius={30}
backgroundColor="white"
position="relative"
>
<Frame
size={60}
borderRadius={30}
center="y"
backgroundColor="white"
shadow="0 1px 5px 0 rgba(0, 0, 0, 0.25)"
drag="x"
dragConstraints={{ left: 0, right: 220 }}
dragElastic={false}
dragMomentum={false}
onDrag={props.onDrag}
/>
</Frame>
)
}
function App() {
let animationControls = useAnimation()
function handleDrag(event, info) {
let newScale = transform(info.point.x, [0, 220], [0.4, 1.5])
animationControls.start({
scale: newScale,
transition: { type: 'spring', velocity: 0 },
})
}
return (
<div
className="App"
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
flexDirection: 'column',
}}
>
<Skinny cheekAnimate={animationControls} />
<Slider onDrag={handleDrag} />
</div>
)
}
const rootElement = document.getElementById('root')
render(<App />, rootElement)
In the next post, we'll add another event depending on how far the slider is dragged using if
and else
!