Motion: [FEATURE] Introduce useTransition for custom animations

Created on 19 Jul 2019  路  6Comments  路  Source: framer/motion

I would like to create a custom animation with my own transition setup that is not a spring transition (e.g. tween or keyframes based).

Currently useSpring exposes a way of creating a MotionValue that can transition to a new value when set; however, there is no way to create a MotionValue that uses a different transition configuration. The use cases are the same for the useSpring hook (just using a different transition).

I could be wrong but I don't think useMotionValue does this right now, it's more of a tracking variable for animations that are occurring.

I'd imagine this would work like useSpring except it would allow for any transition to be specified -- useSpring could then just be derived from useTransition. An alternative could be to expose useTween and useKeyframes, but that feels like it would get annoying. Something like:

// Simple transition
const x = useTransition(0, { duration: 1, delay: 0.5 });
x.set(500); // x transitions to 500 and takes 1s after a 0.5s delay

// Spring transition
const x = useTransition(0, { type: 'spring', ...}); 
feature

Most helpful comment

Hi @BenMagyar. I sort-of see the use-case. useSpring is uniquely useful in how it integrates existing velocities ie https://codesandbox.io/s/framer-motion-usespring-6s2gr.

The problem with the useSpring API in general is you're bound to React's render lifecycle when you want to change the transition. It might be more likely that we offer an imperative animate function ie

const x = useMotionValue(0)

animate(x, 100, transition)

That could conceivably animate one or more values outside of React's lifecycle.

But as we already have ways of declaratively and imperatively animating components I probably won't press on this until there's a way of using this with something other than the DOM, ie a hook that could be used with the canvas.

All 6 comments

Hi @BenMagyar. I sort-of see the use-case. useSpring is uniquely useful in how it integrates existing velocities ie https://codesandbox.io/s/framer-motion-usespring-6s2gr.

The problem with the useSpring API in general is you're bound to React's render lifecycle when you want to change the transition. It might be more likely that we offer an imperative animate function ie

const x = useMotionValue(0)

animate(x, 100, transition)

That could conceivably animate one or more values outside of React's lifecycle.

But as we already have ways of declaratively and imperatively animating components I probably won't press on this until there's a way of using this with something other than the DOM, ie a hook that could be used with the canvas.

I may have worded that oddly, the use case for useTransition would be the same as useSpring (at least in my mind). - here is an example of a useTween with the same idea behind that demo but making a different transition type available - https://codesandbox.io/s/framer-motion-usespring-xzzhh. Two uses cases are shown there one of using it like the box method, and another of being able to use a MotionValue that can be passed to useTransform and set.

The idea of having a useTransition instead was so that any action (whatever comes out of the getAnimation method sans-key https://github.com/framer/motion/blob/master/src/animation/utils/transitions.ts#L132) could be used and specified instead of needing one for each transition type.

I think this is a good example of my hesitancy. It's possible to get a similar effect to this by making an overdamped spring (high damping - stiffness ratio). But it's hard to make a spring of any kind feel weird in this use-case because it always integrates the current velocity.

By contrast, it's a quirk of Motion's default behaviour that the above example works (it uses easeOut by default). Adding ease: easing.easeInOut to the TweenProps and we can already see it's quite easy to create funny behaviour that is representative of most tween configurations when mixed with continuous input.

I see your point, its definitely easy to make some poorly behaving features - but it makes doing something like imitating gravity pretty hard without just retrying values in the spring setup. It doesn't stop anything though - since it's possible to just wrap the underlying tween, etc. from popmotion on my own.

_By the way, Motion is sweet. Thanks!_

I'd find a feature like this useful. I think it would allow developers to do a whole new category of things that they can't do with the existing API. I'll try to explain one use case below:

Consider a list of squares in a row, where each square is absolutely positioned. Each square is positioned using framer-motion's API, and this position might be determined based on the index, like so:

{
  // The index of the square determines the `x` property 
  x: index * 100
}

Assume that a design requirement is that each square must always be spaced 10px apart. They can never overlap, and the spacing should never increase either. Lastly, imagine that a user can take some action (like clicking a button) to scroll the list in an animated fashion.

As long as all of the squares remain mounted, then the existing framer motion API can be used to ensure that the 10px separation is maintained. Just pass the x into the animate prop, and it will work fine.

However, if you introduce virtualization, then you lose the guarantee that the spacing remains consistent. If the user can input the scroll command faster than the animation time, then they can begin to see overlaps if they input too quickly.

This is a crucial point, so to better understand it, let's walk through a series of steps that create overlaps:

  1. A user makes an input, and the squares begin to slide to the left. Because of virtualization, this mounts a new square to the far right that begins to animate in.
  2. Before the transition completes, the user makes another input to slide the squares left a second time.
  3. This mounts a new square at the same location of the previously-mounted square. However, because the transition didn't have time to complete, the two squares are now overlapping.
  4. If the user keeps pressing before the transition ends, then they'll continue to see the squares overlapping until they slide into the correct position with the correct spacing

The problem here is that virtualization necessitates keeping track of _shared state_ of the current animation state of the entire list. Each item in the list cannot be treated independently.

A solution that I considered is keeping track of a single "anchor" value. It is a single number that gets animated, and every square is positioned relative to that anchor. That way, as things mount and are positioned relative to the anchor, they never overlap with one another, as the anchor "stores the state" of the animation.

To accomplish this using the framer-motion API, I was planning to use a standalone MotionValue, but because I require non-spring transitions of my squares, I was unable to.

This feature would open up a whole new category of animation functionality, too, like tying the opacity of an element to its x position to ensure that elements at a certain position _always_ have a particular opacity (similar to how one might tie opacity to the values returned from useViewportScroll, but more general).

I was looking at a feature like this since integrating framer-motion in a project recently.
My use case is animating between 2 numeric values (0 - 1) representing the sound level on a player. This creates a smooth Fade In/Fade Out of the sound instead of an abrupt change.

Since I need to pump the value directly to the player, I cannot use animations outside the react lifecycle (react controls the player) - so no useAnimation :(
I was successful in using useSpring for my use-case, but the animation is a bit weird since the default spring settings make the volume jump around the final value (physics based animations are not so nice in this case).

A useTransition with the same behaviour as useSpring but with durations and easings would be amazing for this use-case.

Many thanks to the maintainers though 鉂わ笍, framer-motion is awesome!

I've tried using popmotion directly and creating my own hook but that lead to #580

Was this page helpful?
0 / 5 - 0 ratings