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

Add animation support #2068

Merged
merged 3 commits into from
Jul 27, 2020
Merged

Add animation support #2068

merged 3 commits into from
Jul 27, 2020

Conversation

adamwathan
Copy link
Member

@adamwathan adamwathan commented Jul 24, 2020

This PR introduces a new animation core plugin that generates a set of animate-{name} classes and the correponding @keyframes declarations.

Live demo:

https://tailwind-animation-playground.vercel.app/

It is configured through both the animation and keyframes sections of your tailwind.config.js theme:

// tailwind.config.js
module.exports = {
  // ...
  theme: {
    animation: {
      none: 'none',
      spin: 'spin 1s linear infinite',
      ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
      pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
      bounce: 'bounce 1s infinite',
    },
    keyframes: {
      spin: {
        from: { transform: 'rotate(0deg)' },
        to: { transform: 'rotate(360deg)' },
      },
      ping: {
        '0%': { transform: 'scale(1)', opacity: '1' },
        '75%, 100%': { transform: 'scale(2)', opacity: '0' },
      },
      pulse: {
        '0%, 100%': { opacity: '1' },
        '50%': { opacity: '.5' },
      },
      bounce: {
        '0%, 100%': {
          transform: 'translateY(-25%)',
          animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
        },
        '50%': {
          transform: 'translateY(0)',
          animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
        },
      },
    },
    // ...
  },
  variants: {
    animation: ['responsive'],
    // ...
  }
  // ...
}

Animation utilities only include responsive variants by default.

Design rationale

Targeting the animation shorthand property

Originally I had planned to implement this feature similar to our transition utilities, where we'd have separate utilities for animation name, duration, timing function, repeat, etc.

I decided against this ultimately because in my explorations I noticed that almost all animations are very specific, and all of the elements of an animation are inherently tightly coupled, making composing animations through several utilities something that is just not that useful in the real world.

Instead I chose to implement this feature by targeting the animation shorthand property directly, so every detail of an animation is specified together in one place.

This has the added benefit of avoiding any naming collision with the transition utilities.

If someone wanted to create variations of a single animation with the parameters changed, they can just create multiple animations, like animate-spin, animate-spin-fast, and animate-spin-slow.

Generating utilities and keyframes from a single plugin

You'll notice that the configuration for the animation property lives in an animation section of the theme, and the @keyframes definitions live in a keyframes section, but there is only one core plugin being added (animation).

I considered creating two plugins (animation and keyframes) but ultimately this felt pedantic. They are always going to be used together and it doesn't add any benefit to control them separately. If anything it would lead to confusion when users disable the animation plugin and are surprised when the @keyframes are still generated. It seemed more aligned with people's expectations to me that they be linked, so I've chosen to generate both from a single plugin, even though it sets a precedent as the first core plugin to read from more than one theme key.

I also considered trying to group the animation and keyframes definitions together using some fancy data structure, but the results also felt unintuitive, and led to other questions that made things feel even more confusing.

Example of what I considered but rejected:

animation: {
  spin: [
    '3s linear infinite',
    {
      from: { transform: 'rotate(0deg)' },
      to: { transform: 'rotate(360deg)' },
    },
  ],
},

Notice how the keyframes can't be named here? Does that mean you leave the name off the animation too, like I have in that example, and have Tailwind add it automatically? Or do you manually add it yourself, knowing it has to match the key name? Too much weird magic, no good.

Included animations

I've chosen to bake in four default animations:

  • animate-spin, which is a simple infinite spin, for loading spinners and stuff
  • animate-ping, which is a scale/fade animation that looks like a little radar blip
  • animate-pulse, which is a subtle opacity pulse, useful for skeleton screens
  • animate-bounce, which is a playful bounce, useful for arrows and stuff

You can preview them here:

https://tailwind-animation-playground.vercel.app/

They are all infinite looping animations, and were the only ideas I could come up with that felt general purpose enough to include by default. Even then, I expect all of these baked in animations are only going to be useful in very specific situations, so I'm choosing to think of them much more like example animations than a strongly recommended universal animation system or something.

The nature of animations is that they tend to be very specific to a given design, so I think it is inevitable that people will be adding their own to support the specific designs they are working on. Adding animations as a feature to Tailwind at least gives them an idiomatic approach for introducing animations, and makes their custom animations feel like they "belong" in the system better than if they were to just throw them in their custom CSS file.

@ptrin
Copy link

ptrin commented Jul 24, 2020

It would be great if the animations could respect the user's prefers-reduced-motion setting:
https://web.dev/prefers-reduced-motion/

@adamwathan
Copy link
Member Author

It would be great if the animations could respect the user's prefers-reduced-motion setting:

Agree, and would be good to add support for this to transitions as well. I don't want to block merging this feature on that, but I can look into adding it as a separate feature in the near future.

I've read conflicting information about it in my research, so need to study a bit more to figure out what the right approach is.

Bootstrap for example respects it on transitions but they don't for loading spinners, and this was a deliberate choice:

twbs/bootstrap#30081

I think we will probably go with a variant-based solution, where you can do stuff like animate-spin reduce-motion:animate-none, just like how we handle media queries, focus states, etc. This would make the solution work with transitions as well.

@ludo237
Copy link

ludo237 commented Jul 24, 2020

Is it possible to leverage the duration and transform classes that are already in place to define the animation object?

@TanzimIbthesam

This comment has been minimized.

@AKOPWeb

This comment has been minimized.

@estevanmaito
Copy link
Contributor

Instead of fade I would name it pulse, based on my limited experience with the libraries/guidelines below.

Fade is usually used in animations as a fade-in or fade-out, in the sense that something is appearing or disappearing. The example (and the code being infinite), IMO falls out of this and could lead to misconception.

  • Animate.css for example, specifically puts fade animations under Fading entrances and Fading exits.

  • This arcticle (Everything you need to know about skeleton screens), on The tests section, names this kind of animation pulse

  • Material UI also uses pulse (they refer to it as pulsating).

  • Google's Material Guidelines defines fade this way: "The fade pattern is used for UI elements that enter or exit within the bounds of the screen, such as a dialog that fades in and out of view from the center of a screen."

If users were to extend animations with real fade-in and out options, the default fade could cause confusion, because it doesn't have an end state.

@adamwathan
Copy link
Member Author

Agree on renaming fade to pulse, done 👍

@dillingham
Copy link

dillingham commented Jul 24, 2020

Exciting stuff. Wondering about some sort of fluent interface to build up the config options

animate-red

animation: [
    animate(‘red’)->frames([
        from(‘bg-red-200’)->to(‘bg-red-500’),
        from(‘bg-red-500’)->to(‘bg-red-200’),
    ])
]

Edit: also could allow using tailwind classes and converting to their values using the config vs hard coded css (mt-10 vs margin-top: 4rem and add only one section to the config AND allow some sensible defaults with methods to override for things like duration and easing

@sbarfurth
Copy link

While I like the idea @dillingham, I think that is very much out of scope for Tailwind. My feeling anyway.

@adamwathan
Copy link
Member Author

Added a PR for a new reduce-motion variant:

#2071

@KaniRobinson
Copy link

👏

@adamwathan adamwathan merged commit e29e960 into master Jul 27, 2020
@surjithctly
Copy link

@adamwathan Is it possible to add "Shake" animation as default animation?
Because I can imagine a lot of interesting use cases such as form validations.

  theme: {
      animation: {
        shake: "shake 0.82s cubic-bezier(.36,.07,.19,.97) both",
      },
      keyframes: {
        shake: {
          "10%, 90%": { transform: "translate3d(-1px, 0, 0)" },
          "20%, 80%": { transform: "translate3d(2px, 0, 0)" },
          "30%, 50%, 70%": { transform: "translate3d(-4px, 0, 0)" },
          " 40%, 60%": { transform: "translate3d(4px, 0, 0)" },
        },
      },
  },

@adamwathan adamwathan deleted the animation-utilities branch August 15, 2020 22:08
@brandonpittman
Copy link

@adamwathan I would love to be able to add animation-delay to the baked in animations.

@ecklf
Copy link
Contributor

ecklf commented Oct 13, 2020

^ +1, I find myself extending this quite often via plugin

@brandonpittman
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.