Framer » Animation » While Hover and While Tap

While Hover and While Tap

These two are an easy way to add interactivity to a canvas element. With just one line of code, you add an automatic animation between two states.

📘 The Framer book – 💫 Animate, Transition, WhileHover, & WhileTap.framerx

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

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 for tap gestures.

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

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. And the same thing happens when you release; the grow animation also has a duration of 0.5.

📑 whileTap

Both

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

export function WhileHover_WhileTap(): Override {
    return {
        whileHover: { radius: 75 },
        whileTap: { backgroundColor: "Gold" },
    }
}

C03 – WhileHover and WhileTap

Initial

Say I would like the frame to fade in when it first appears. We know how to do that: You just add a standard animate that changes the opacity:

export function Fade_In(): Override {
    return {
        animate: { opacity: 1 },
        whileHover: { radius: 75 },
        whileTap: { backgroundColor: "Gold" },
    }
}

… and then, because the frame should initially be invisible, you change its opacity on the canvas to zero.

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(): Override {
    return {
        initial: { opacity: 0 },
        animate: { opacity: 1 },
        whileHover: { radius: 75 },
        whileTap: { backgroundColor: "Gold" },
    }
}

C04 – Initial

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

📑 initial

Including transition settings

You know that you can set a frame’s animation options with transition. But our frame now has three animations, what if we only want to slow down the animate animation?

For this, you can include a transition object directly with the animation:

export function Including_Transition_settings(): Override {
    return {
        initial: { opacity: 0 },
        animate: { opacity: 1, transition: { duration: 1 } },
        whileHover: {
            backgroundColor: "Gold",
            transition: { ease: "easeInOut", yoyo: Infinity },
        },
        whileTap: { backgroundColor: "Orange" },
    }
}

The frame’s fade-in animation will now last 1 second, and the whileHover is changed to a repeating yoyo animation with an "easeInOut" curve.

C05 – Including Transition settings

This kind of included transition settings will override the global settings you might have set on the frame’s transition property.

Initial: false

One more thing about initial: Setting it to false will cancel the animate animation.

export function Initial_false(): Override {
    return {
        initial: false,
        animate: { rotate: 45 },
        whileHover: { radius: 75 },
        whileTap: { backgroundColor: "Gold" },
    }
}

Here, the frame will not rotate to 45 degrees, it’ll just be displayed already turned, without animation.

C06 – Initial: false

Naturally, when you comment out the initial: false, it will animate :

export function Initial_false(): Override {
    return {
        // initial: false,
        animate: { rotate: 45 },
        whileHover: { radius: 75 },
        whileTap: { backgroundColor: "Gold" },
    }
}

But why would you even use initial: false? You could instead get rid of that animate line and just rotate the frame 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 with initial: 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 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