Animation » ‘While’ Animations and Initial

‘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.

📘 The Framer book – 💫 A: Animate | B: Transition | C: ‘while’ animations & Initial

 

While Hover

Here’s an override example: You give whileHover 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 }} />
    }
}

C01 – WhileHover

The frame shrinks to 80 percent of its size when you hover over it, and it grows again when the hover ends.

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.

Animation helpers, whileHover

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 }}
            />
        )
    }
}
C02 – WhileTap

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.

Animation helpers, whileTap

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 }}
            />
        )
    }
}
C03 – WhileDrag

  • 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 its scale of 110% and a larger boxShadow.

(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.

whileDrag

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={{ duration: 2, repeat: Infinity, ease: "linear" }}
    />
  )
}
C04 – WhileFocus

whileFocus
: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:

Leave a space in the text to see the error state — Open in new tab

  • 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.

onblur

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 }}
            />
        )
    }
}
C05 – Initial

(Hit to see that fade-in animation again.)

initial

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 with initial.

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.

Setting the original borderRadius with initial (so that the whileHover knows what to animate from) seems to solve it.

export default function Example(props) {
    return (
        <div>
            <motion.div
                style={{
                    width: 150,
                    height: 150,
                    borderRadius: 30,
                    backgroundColor: "#fff",
                }}
                initial={{ borderRadius: 30 }}
                whileHover={{ borderRadius: 75 }}
            />
        </div>
    )
}

Animating 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.

C06 – Including Transition settings

Transition, transition

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.

C07 – Initial: false

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.

initial


6 comments on “‘While’ Animations and Initial”

  • william.jm.harvey says:

    How do you switch between two different variants within the “animate:” property?

    • Tes Mat says:

      By making the value you pass to animate dynamic.

      Here’s an example where it changes depending on another Frame being dragged or not:

      import { Override, Data } from "framer"
      
      const data = Data({
          dragging: false,
      })
      
      export function Background(): Override {
          return {
              variants: {
                  dragging: { background: "red" },
                  static: { background: "blue" },
              },
              animate: data.dragging ? "dragging" : "static",
          }
      }
      
      export function Draggable(): Override {
          return {
              drag: true,
              onDragStart() {
                  data.dragging = true
              },
              onDragEnd() {
                  data.dragging = false
              },
          }
      }

      Or you pass the animate property the variable that’s provide by a useState() 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 an useAnimation() hook, so you can use start(). E.g., animation.start("variantLabel")

      • william.jm.harvey says:

        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?

        • Tes Mat says:

          Hard to tell what might be off without seeing your code, of course.

          About start(): Check the useAnimation() page for more about the AnimationControls object (and its start() function).

          But that page hasn’t any examples with variants, so I’ll add one here:

          import { Override, useAnimation } from "framer"
          
          export function Starting_and_stopping_2(): Override {
              const animation = useAnimation()
          
              return {
                  animate: animation,
                  variants: { hoverStart: { scale: 1.25 }, hoverEnd: { scale: 1 } },
                  onHoverStart() {
                      animation.start("hoverStart")
                  },
                  onHoverEnd() {
                      animation.start("hoverEnd")
                  },
              }
          }

          This a variants version of the first example on that page. Actually just a recreation of whileHover with the onHoverStart() and onHoverEnd() events.

  • Joe Preston says:

    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?

    • Tes Mat says:

      Not 100% sure what you mean, because you’re asking this on this whileHover and whileTap page. You might mean something like this:

      import { Override, Data } from "framer"
      
      const appState = Data({
          tapDown: false,
      })
      
      export function TapMe(): Override {
          return {
              onTapStart() {
                  appState.tapDown = true
              },
              onTap() {
                  appState.tapDown = false
              },
          }
      }
      
      export function AnimateMe(): Override {
          return {
              animate: { opacity: appState.tapDown ? 0 : 1 },
          }
      }

      … which is like a whileTap, but then from one Frame sent to another.

      But you probably mean something like this:

      const appState = Data({
          tapped: false,
      })
      
      export function TapMe(): Override {
          return {
              onTap() {
                  appState.tapped = true
                  setTimeout(() => {
                      appState.tapped = false
                  }, 1000)
              },
          }
      }
      
      export function AnimateMe(): Override {
          return {
              animate: { opacity: appState.tapped ? [0, 1, 0] : 0 },
              transition: { duration: 1 },
          }
      }

      Here the onTap() on one Frame starts a Keyframe animation on another Frame. (And appState.tapped is then reset to false a minute after that, so that you can trigger the animation again.)

Leave a Reply