Framer X » Animation » While Hover and While Tap

While Hover and While Tap

This is absolutely the easiest way to add interactivity to a Frame or Design Component. With just one line of code, you add an automatic animation between two states.

download the project with the examples

While Hover

Here’s an Override example. You give whileHover an object with the properties you want to animate, just like you did earlier with animate.

import { Override } from "framer"

export function WhileHover(): Override {
    return {
        whileHover: { scale: 0.8 },
    }
}
C01 – WhileHover

The Frame shrinks to 80% of its size when you hover over it and grows again when the hover ends.

With this 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.

And here’s how you do it in a Code Component:

export function C01_WhileHover() {
    return (
        <Frame
            whileHover={{ scale: 0.8 }}
        />
    )
}

(The {{double curly braces}} are there because you’re writing the object directly inline.)

📑 whileHover

While Tap

The second one, whileTap, works the same, but then for taps.

And just like with animate, you can tweak these animations by adding transition settings.

import { Override } from "framer"

export function WhileTap(): Override {
    return {
        whileTap: { scale: 0.8 },
        transition: { duration: 0.5 },
    }
}
C02 – WhileTap

Here the Frame shrinks when you press down, but it will take its sweet little time of half a second to do it.

Also, the same thing happens when you release, the grow animation also has a duration of 0.5.

📑 whileTap

Both

Naturally, you can also use both whileHover and whileTap at the same time:

import { Override } from "framer"

export function WhileHover_WhileTap(): Override {
    return {
        whileHover: { radius: 75 },
        whileTap: { background: "Gold" },
    }
}
C03 – WhileHover and WhileTap

Variants

Now that we’ve already seen three ways to start an animation, we can talk about Variants.

You usually give animate (or whileHover, whileTap) an object with properties to animate to, something like, e.g. { rotate: 180 }.

With Variants, you can give those animation states a name, a variant label, and you tell animate (or whileHover, whileTap,…) to animate to that label.

const variants = {
    start: { rotate: 180 },
    hoverState: { radius: 75 },
    tapState: { background: "Gold" },
}

export function Variants(): Override {
    return {
        variants: variants,
        animate: "start",
        whileHover: "hoverState",
        whileTap: "tapState",
    }
}
C04 – Variants

For this to work you also need to provide the Frame with the variants themselves, though, so it knows that e.g., "start" stands for { rotate: 180 }.

Here the variants are written in a separate object (called variants), but as you’ll see in the next example, you can also write them inline.

Remember States?

In Framer Classic we had something similar to Variants: States.

However, ‘states’ already has a meaning in the React world (think of useState()), so the Framer team picked a different name.

As another aside: The Framer animation API is (partly) based on Popmotion’s Pose, an animation library for React. That library’s poses are the predecessor of Framer’s Variants.

📑 variants

Children

It gets even better when your Frame has children. When you give a child a set of Variants (with the same labels), it will animate simultaneously, automatically.

Here the Variants() Override is applied to the parent and Variants_Children() to the child.

// Override for the parent
export function Variants(): Override {
    return {
        variants: {
            start: { rotate: 180 },
            hoverState: { radius: 75 },
            tapState: { background: "Gold" },
        },
        animate: "start",
        whileHover: "hoverState",
        whileTap: "tapState",
    }
}

const childVariants = {
    start: { rotate: -180 },
    hoverState: { radius: 30 },
    tapState: { background: "Red" },
}

// Override for the child
export function Variants_Children(): Override {
    return {
        variants: childVariants,
    }
}
C05 – Variants and Children

The animatewhileHover, and whileTap properties are only set on the parent; all that the child needs is a set of (identically named) variants for this to work.

The parent’s "start" animation is a rotate to 180, but the child rotates to -180. Because of child’s counterclockwise rotation, it doesn’t seem to rotate at all.

Note: In the parent’s Override, I now wrote the variants inline (instead of in a separate const object).

Orchestration

You can orchestrate child animations with these transition settings:

  • with delayChildren, you insert a delay between the parent and child animations;
  • with staggerChildren, you add delays between the children animations themselves;
  • with staggerDirection, you decide which child to start with;
  • and with when, you can have the parent animate after the children.

This next example has three child Frames. Because of the delayChildren setting, the child animations start 0.2 seconds after the parent. And between the child animations is also a staggerChildren delay of 0.2 seconds.

// Override for the parent
export function Orchestration(): Override {
    return {
        variants: {
            initial: { skewX: -10, opacity: 0 },
            start: { skewX: 0, opacity: 1 },
            hoverState: { scale: 1.05 },
            tapState: {},
        },
        initial: "initial",
        animate: "start",
        whileHover: "hoverState",
        whileTap: "tapState",
        transition: {
            delayChildren: 0.2,
            staggerChildren: 0.2,
        },
    }
}

// Override for the children
export function Orchestration_Children(): Override {
    return {
        variants: {
            initial: { x: 50, opacity: 0 },
            start: { x: 0, opacity: 1 },
            tapState: { background: "Red" },
        },
    }
}
C06 – Orchestration

Note: only the parent has a "hoverState" variant label.

And also: the parent doesn’t have a "tapState" animation (there’s an empty {} object), but by setting a whileTap for the parent, the children do have one (with the same staggerChildren delay between them).

📑 Orchestration, delayChildren, staggerChildren, staggerDirection, when

Initial

One more thing about this last example: It uses the initial property, which lets you define how a Frame should look when it appears.

initial accepts the same kind of values as animate, whileTap, etc. And as you can see: also variant labels.

It’s instrumental when you want an animation to start from opacity: 0 without having to dial down the Frame’s opacity on the Canvas.

📑 initial


6 comments on “While Hover and While Tap”

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

      Instead of the resetting with a setTimeout() could also use an useAnimation() hook to drive the animation, but it’s kind of a hack to share an useAnimation() through the Data object. (But it’s possible, here’s an example.)

Leave a Reply