Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animation System: Making things move #4

Closed
ItsJonQ opened this issue Jul 21, 2020 · 13 comments
Closed

Animation System: Making things move #4

ItsJonQ opened this issue Jul 21, 2020 · 13 comments
Assignees

Comments

@ItsJonQ
Copy link
Owner

ItsJonQ commented Jul 21, 2020

This one is an interesting one. It's the most exciting one for me (I like making things move), but arguably, the most difficult. The web is severely lacking when it comes to sequencing animations within UI, especially if it needs to handle mounting/unmounting (and diffing!).

What makes a good animation system?

Animations are hard. Really hard. These complexities are often felt by developers when trying to wrestle various settings and states together. It's more difficult when outside state is involved.

Here are some things that I think make a good animation system:

  • Minimal APIs (e.g. Component props, hooks, etc...)
  • Automatic mount/unmount handling
  • Ability to sequence across multiple items (e.g. staggering items that load)
  • High performance (as little repaint + layout calc as possible)
  • Persisting elements across transitions
  • Seamless animation cancelling
  • Lifecycle hooks (e.g. onEnter, onBeforeLeave)
  • Lightweight (library size. This is... often the killer)

What I've Used

Previously, I've examined and used several libraries and engines, including Anime.js, React Spring, Pose, and others.

There are aspects I like in all of them. However, I've always felt like I had to make considerable (undesirable) trade-offs in one form or another. This often takes the form of overly verbose animation setups.

One library that shows the most potential is Framer Motion.

The APIs for Frame Motion is beautiful. It's very minimal. The provided motion.div components are incredibly intelligent. They know how to respond in isolation and with other animated components in sequence and in context.

The biggest downside (that I can see) is the library's size. It's approx 28-29KB gzipped. Ouch.

However, the feature-set may be worth it 🤞 .


I think when we're ready to experiment with animations, it would be interesting to give Framer Motion a go 👍

@ItsJonQ ItsJonQ self-assigned this Jul 21, 2020
@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 21, 2020

This issues describes the Animation part of the core systems of a component library.

@diegohaz
Copy link
Collaborator

I'll just quote my comment about Framer Motion on #3 (comment) here, but I would like to add that I totally agree with giving it a go. This is just my initial thoughts, but I don't have enough experience with the library. So I'm curious to see how this goes. 😊

I agree that Framer Motion looks better, but if I had to choose between Framer Motion and React Spring to use in a component library, I would probably go for the one that has the most flexible API and gives me more control. So, in this case, I would use React Spring internally and maybe expose an API similar to Framer Motion to my consumers.

If we ever need to build something different, Framer Motion may not have a prop that supports it. Whereas with a lower level API like the one React Spring exposes, we have more power over what we can do. But I don't know Framer Motion enough to discard it. Maybe it exposes some lower level APIs just like Emotion.

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 21, 2020

This is just my initial thoughts, but I don't have enough experience with the library

Same! It'll be interesting to experiment with 🍿

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 31, 2020

I just tried implementing a drag sortable list with removable items.

The solution involved combining react-sortable-hoc and react-spring.

It okays... slightly less than okay.

Screen Capture on 2020-07-31 at 11-03-46

The bit I'm struggling with is automatically animating items in/out as they are added/removed from the list.

React Spring's solution is to their useTransition hook to handle mounting/unmounting items.

It does not know how to animate a "collapse" animation.

Imagine removing an item, it's height collapses from starting (e.g. 35px) to 0px.
Doing this would involve using react-use-measure, tracking individual item height state, etc...

I haven't tried Framer Motion, but it looks like we may have to do something similar to achieve this effect.

(😅 This is what I'm talkin' about when I've mentioned that Animation libraries should be easy to use. That being said, I'm aware of the difficulties in auto-animating height transitions).

Custom Solution?

A couple of years ago, I created a tiny library to handle this very thing. Easily (and almost automatically) handle transitions of items mounting and unmounting:

https://github.com/helpscout/motion

It handled the mount cycle part of React rendering, using anime.js under the hood as it's animation engine.

Creating a mount/unmount wrapper looked like this:
https://github.com/helpscout/motion/blob/master/stories/Inbox.stories.js#L122

(Forgive the verboseness. This was during React 15 times, before hooks and performant functional components)

Result:
https://helpscout-motion.netlify.app/?selectedKind=Inbox&selectedStory=Example&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel

I may revisit this 🤔

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 31, 2020

I just revisited react-flip-toolkit:

https://github.com/aholachek/react-flip-toolkit

I remember poking at it when it was announced a couple of years ago.

Maybe some to draw some ideas from :)

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 31, 2020

Welp! It looks like Framer Motion handles this beautifully!

Add I need to add for this to happen is height: 0 as an exit prop.

<motion.div exit={{height: 0}}>...</motion.div>

Screen Capture on 2020-07-31 at 14-27-37

This implementation alone might make Framer Motion the winner library 😂

(Seriously. I'm ecstatic with how this works. Coming from someone who has spent many manyyy hours working on/with animation engines and frameworks)

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 31, 2020

Drag sorting looks pretty simple to implement as well! ❤️
https://codesandbox.io/s/framer-motion-drag-to-reorder-pkm1k?file=/src/Example.tsx

Update: Nevermind. It's broken in Framer Motion v2. It's using some APIs that are no longer supported:
https://www.framer.com/api/motion/guide-upgrade-to-framer-motion-2/#drag

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Jul 31, 2020

I'm feeling pretty good about Framer Motion. Perhaps we can use it for the most Animation use cases. However, drag sorting can be accomplished by another library (like react-sortable-hoc). Our components can help these libraries blend together seamlessly

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Aug 1, 2020

Ooooo boy! Framer Motion + React Sortable HOC:

They work together nicely! (Code wise)

Screen Capture on 2020-08-01 at 13-37-36

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Aug 2, 2020

The built-in FLIP support for Framer Motion is incredible. Absolutely incredible.

The following was (basically) enabled by adding layout to a <motion.div /> component:

Screen Capture on 2020-08-02 at 11-41-46

This is the code that's needed to create this experience:

const App = () => {
  const [items, setItems] = useState(itemSchema.make(10));

  const remove = (id) => {
    setItems(items.filter((item) => item.id !== id));
  };

  const add = () => {
    setItems([...items, itemSchema.makeOne()]);
  };

  const shuffle = () => {
    const next = items.sort(() => Math.random() - 0.5);
    setItems([...next]);
  };

  return (
    <View css={{ margin: '20px auto', maxWidth: 500 }}>
      <Spacer>
        <Flex>
          <Button onClick={add}>Add User</Button>
          <Button onClick={shuffle}>Shuffle</Button>
        </Flex>
      </Spacer>
      <Grid columns={3}>
        {items.map((item, index) => (
          <Card
            animate={{
              opacity: 1,
              y: 0,
            }}
            as={motion.div}
            exit={{
              opacity: 0,
            }}
            initial={{ opacity: 0, y: 10 }}
            key={item.id}
            layout
          >
            <View css={{ padding: 8 }}>
              <Flex>
                <Text>{item.name}</Text>
                <Button
                  icon={<View>X</View>}
                  onClick={() => remove(item.id)}
                  size="small"
                  variant="tertiary"
                />
              </Flex>
            </View>
          </Card>
        ))}
      </Grid>
    </View>
  );
};

@diegohaz
Copy link
Collaborator

diegohaz commented Aug 2, 2020

That's looking super nice! Could you put together a CodeSandbox with that code? I think that would be a great test case for the sorting functionality on the Reakit Composite that I'm working on. :)

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Aug 3, 2020

That's looking super nice! Could you put together a CodeSandbox with that code?

@diegohaz You bet!!!

Here it is 😊
https://codesandbox.io/s/g2-animations-motion-flip-sort-example-f8p0v?file=/src/App.js

@ItsJonQ
Copy link
Owner Author

ItsJonQ commented Aug 27, 2020

Closing this up as the Animation x Style systems are feeling pretty good!

https://github.com/ItsJonQ/g2/tree/master/packages/animations

@ItsJonQ ItsJonQ closed this as completed Aug 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants