‘While’ Animations and Initial
With the whileHover
, whileTap
, and whileDrag
gesture animations, you can quickly make layers interactive. With just one line of code, you add an automatic animation between two states.
And another property, initial
, lets you decide which values a layer should animate from.
- While Hover
- While Tap
- While Drag
- While Focus
- Initial
- Bug when using
borderRadius
in a ‘while’ animation - Including transition settings
- Setting
initial
tofalse
While Hover
Here’s an override example: You give
an object with the properties you want to animate, just like we did earlier with animate
.
export function WhileHover(Component): ComponentType {
return (props) => {
return <Component {...props} whileHover={{ scale: 0.8 }} />
}
}
The frame shrinks to 80 percent of its size when you hover over
With one line of code, you’ve added two animations:
- one that happens when the pointer is over the frame;
- and a reverse animation that runs when the pointer leaves the frame.
While Tap
The second one, whileTap
, works the same, but for tap gestures.
export function WhileTap(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
whileTap={{ scale: 0.8 }}
transition={{ duration: 0.5 }}
/>
)
}
}
And just like with animate
, you can tweak these animations by adding transition
settings. For example, here, the frame will take its sweet little time of half a second to shrink when you press down. And the same thing happens when you release; the grow animation also has a duration
of 0.5
.
While Drag
Framer Motion 3 added a whileDrag
interaction. It works as you would expect.
Naturally, you can combine these gesture animations. Here we have a draggable frame with whileHover
, whileTap
, and whileDrag
:
export function WhileDrag(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
drag
whileHover={{ opacity: 1 }}
whileTap={{
opacity: 1,
scale: 1.05,
boxShadow: "0px 5px 8px #037",
}}
whileDrag={{ scale: 1.1, boxShadow: "0px 10px 16px #037" }}
transition={{ duration: 0.6 }}
/>
)
}
}
whileHover
— I gave the frame an opacity of 70% on the canvas, which changes to 100% when you hover over it.whileTap
— Tapping the frame makes it grow slightly (scale
of 105%) and gives it a shadow.whileDrag
— Once you start dragging, it seems to float a bit higher above the surface because of itsscale
of 110% and a largerboxShadow
.
(The whileTap
also changes the opacity
to 1
. This is for touch devices, on which a whileHover
is not triggered.)
All three animations have a duration of 0.6
seconds, a bit longer than the default 0.3
.
While Focus
Framer Motion 3.1 added whileFocus
. Only HTML input elements can have a focus state, so you have to use a <motion.input>
.
I tried to make something that’s impossible with CSS’s :focus
pseudo-class and arrived at this crazy background animation that cycles through keyframes of different gradients.
export function C04WhileFocusFM() {
return (
<motion.input
style={{
// width, background, etc.
}}
whileFocus={{
background: [
"linear-gradient(to right, #f0f -200%, #0ff -100%, #f0f 0%, #0ff 100%)",
"linear-gradient(to right, #f0f -100%, #0ff 0%, #f0f 100%, #0ff 200%)",
"linear-gradient(to right, #f0f 0%, #0ff 100%, #f0f 200%, #0ff 300%)",
],
}}
transition={{
background: {
duration: 2,
repeat: Infinity,
ease: "linear",
from:
"linear-gradient(to right, #fff -200%, #fff -100%, #fff 0%, #fff 100%)",
},
}}
/>
)
}
(Apparently, adding the from
value keeps it from continuing to animate (in Framer) when the input field has already lost focus.)
whileFocus
with validation example
In the tweet announcing whileFocus
, Matt Perry added this example that gives a visual indication of the field’s validation status:
- Blue: Neutral
- Green: Valid value entered
- Red: Invalid value entered (no spaces allowed!)
It has an onBlur()
event (called when the element loses focus) that changes a validity
state, which, in turn, defines which color is used for the boxShadow
around the field.
Initial
Let’s return to our While Drag example.
Say I would like the frame to fade in when it first appears. We know how to do that: You make the frame invisible on the canvas by dialing its opacity down to zero, and you animate
it to the desired opacity
:
export function Fade_In(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
drag
animate={{ opacity: 0.7 }}
whileHover={{ opacity: 1 }}
whileTap={{
opacity: 1,
scale: 1.05,
boxShadow: "0px 5px 8px #207",
}}
whileDrag={{ scale: 1.1, boxShadow: "0px 10px 16px #207" }}
transition={{ duration: 0.6 }}
/>
)
}
}
But it’s annoying to have to hide frames on the canvas. You might forget they’re even there!
There’s a solution, though. With the initial
property, you can define which values the frame should animate from.
export function Initial(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
drag
initial={{ opacity: 0 }}
animate={{ opacity: 0.7 }}
whileHover={{ opacity: 1 }}
whileTap={{
opacity: 1,
scale: 1.05,
boxShadow: "0px 5px 8px #207",
}}
whileDrag={{ scale: 1.1, boxShadow: "0px 10px 16px #207" }}
transition={{ duration: 0.6 }}
/>
)
}
}
(Hit to see that fade-in animation again.)
Bug when using borderRadius
in a ‘while’ animation
You’ll run into a bug when animating borderRadius
with a whileHover
, whileTap
, or whileDrag
. But there’s a workaround.
Say you’re doing this:
export default function Example(props) {
return (
<div>
<motion.div
style={{
width: 150,
height: 150,
borderRadius: 30,
backgroundColor: "#fff",
}}
whileHover={{ borderRadius: 75 }}
/>
</div>
)
}
That will simply not work in the current version of Framer. And in a CodeSandbox, using Framer Motion, you’ll get an error when you try to hover over the square.
Framer Motion seems to be confused about which value type you’re using. But you can solve this misunderstanding by explicitly using pixel values (like you would when writing CSS outside of JavaScript).
export default function Example(props) {
return (
<div>
<motion.div
style={{
width: 150,
height: 150,
borderRadius: "30px",
backgroundColor: "#fff",
}}
whileHover={{ borderRadius: "75px" }}
/>
</div>
)
}
borderRadius
with a whileHover
Another solution is to animate the different border radii individually.
Including transition settings
Let’s return to the example with whileHover
, whileTap
, and whileDrag
. I used transition
to set the duration
for all its (four) animations. But what if I want the animate
to last a bit longer than that 0.6
seconds?
For this, we can include a transition
object directly with the animation:
export function Including_transition_settings(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
drag
initial={{ opacity: 0 }}
animate={{ opacity: 0.7, transition: { duration: 1 } }}
whileHover={{ opacity: 1 }}
whileTap={{
opacity: 1,
scale: 1.05,
boxShadow: "0px 5px 8px #307",
}}
whileDrag={{ scale: 1.1, boxShadow: "0px 10px 16px #307" }}
transition={{ duration: 0.6 }}
/>
)
}
}
This kind of included transition settings will override the global settings you’ve set with the transition
property. The frame’s fade-in animation will now last 1
second, while the others (whileHover
, whileTap
, and whileDrag
) stay at 0.6
seconds.
Setting initial
to false
One more thing about initial
: Setting it to false
will cancel the animate
animation.
export function Initial_false(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
drag
initial={false}
animate={{ borderRadius: "50%" }}
whileHover={{ opacity: 1 }}
whileTap={{
opacity: 1,
scale: 1.05,
boxShadow: "0px 5px 8px #407",
}}
whileDrag={{ scale: 1.1, boxShadow: "0px 10px 16px #407" }}
transition={{ duration: 0.6 }}
/>
)
}
}
Here the frame will not animate to a borderRadius
of 50%
; it’ll just be displayed already a circle, without initial animation.
Naturally, when you comment out the initial: false
, it will animate :
export function Initial_false(Component): ComponentType {
return (props) => {
return (
<Component
{...props}
drag
// initial={false}
animate={{ borderRadius: "50%" }}
whileHover={{ opacity: 1 }}
whileTap={{
opacity: 1,
scale: 1.05,
boxShadow: "0px 5px 8px #407",
}}
whileDrag={{ scale: 1.1, boxShadow: "0px 10px 16px #407" }}
transition={{ duration: 0.6 }}
/>
)
}
}
But why would you even use initial={false}
? You could instead delete that animate
line and make it a circle on the canvas.
Further ahead, you’ll see that you need not animate
to fixed, pre-defined values. You can animate to a dynamic value provided by one of the hooks. And by setting initial
to false
, you can then suppress all initial animations, whatever that dynamic value might be. There’s an example of this in The useCycle() Hook.
6 comments on “‘While’ Animations and Initial”
Leave a Reply
You must be logged in to post a comment.
How do you switch between two different variants within the “animate:” property?
By making the value you pass to
animate
dynamic.Here’s an example where it changes depending on another Frame being dragged or not:
Or you pass the
animate
property the variable that’s provide by auseState()
hook (animate: state
), so you can dynamically change it to a different variant label, e.g.setState("variantLabel")
Or you give
animate
the AnimationControls object provided by anuseAnimation()
hook, so you can usestart()
. E.g.,animation.start("variantLabel")
Hi Tes, Hmm… when switching between the two variants based on logic, there is no animation triggered. Would this be solved with the animation.start option you mention above? If so, Could you give an example of that?
Hard to tell what might be off without seeing your code, of course.
About
start()
: Check theuseAnimation()
page for more about the AnimationControls object (and itsstart()
function).But that page hasn’t any examples with variants, so I’ll add one here:
This a variants version of the first example on that page. Actually just a recreation of
whileHover
with theonHoverStart()
andonHoverEnd()
events.Can you use onTap in one override to trigger a sequence in another override? Say to tap a card a fade something up from 0 to 1 and back down to 0?
Not 100% sure what you mean, because you’re asking this on this
whileHover
andwhileTap
page. You might mean something like this:… which is like a
whileTap
, but then from one Frame sent to another.But you probably mean something like this:
Here the
onTap()
on one Frame starts a Keyframe animation on another Frame. (AndappState.tapped
is then reset tofalse
a minute after that, so that you can trigger the animation again.)