Shopify / polaris

Shopify’s design system to help us work together to build a great experience for all of our merchants.
https://polaris.shopify.com
Other
5.76k stars 1.17k forks source link

Move easing() and duration() function values to tokens #4595

Closed alex-page closed 2 years ago

lgriffee commented 2 years ago

Audit Findings

I conducted a quick audit of all duration() and easing() instances in polaris-react.

Below is a brief summary of those findings:

Duration

Semantic Name Value Instances
duration(none) 0 0
duration(fast) 100ms 1 result / 1 file
duration() / duration(base) 200ms 24 results / 13 files
duration(slow) 300ms 3 results / 2 files
duration(slower) 400ms 2 results / 1 file
duration(slowest) 500ms 5 results / 4 files
--p-duration-100 100ms 25 results / 14 files
--p-duration-150 150ms 11 results / 6 files

Easing

Semantic Name Value Instances
easing() / easing(base) cubic-bezier(0.25, 0.1, 0.25, 1) 29 results / 16 files
easing(in) cubic-bezier(0.36, 0, 1, 1) 2 results / 2 files
easing(out) cubic-bezier(0, 0, 0.42, 1) 4 results / 3 files
easing(excite) cubic-bezier(0.18, 0.67, 0.6, 1.22) 0
easing(overshoot) cubic-bezier(0.07, 0.28, 0.32, 1.22) 0
easing(anticipate) cubic-bezier(0.38, -0.4, 0.88, 0.65) 0
--p-ease-in cubic-bezier(0.5, 0.1, 1, 1) 2 results / 2 files (only doc. files)
--p-ease cubic-bezier(0.4, 0.22, 0.28, 1) 24 results / 9 files

Proposed Token Values

Based on the values currently being used in the repo, here are some possible token names:

Duration

Value Token Name (Option 1) Token Name (Option 2)
0ms --p-duration-0 --p-time-0
100ms --p-duration-100 --p-time-100
150ms --p-duration-150 --p-time-150
200ms --p-duration-200 --p-time-200
300ms --p-duration-300 --p-time-300
400ms --p-duration-400 --p-time-400
500ms --p-duration-500 --p-time-500

Easing

Value Token Name (Option 1) Token Name (Option 2)
cubic-bezier(0.25, 0.1, 0.25, 1)
OR
cubic-bezier(0.4, 0.22, 0.28, 1)
--p-easing-ease --p-easing-default
cubic-bezier(0.36, 0, 1, 1)
OR
cubic-bezier(0.5, 0.1, 1, 1)
--p-easing-ease-in --p-easing-accelerate
cubic-bezier(0, 0, 0.42, 1) --p-easing-ease-out --p-easing-decelerate
\?? --p-easing-ease-in-out --p-easing-symmetric
\?? --p-easing-linear --p-easing-constant

Questions

@johanstromqvist This was a quick and scrappy attempt at trying to bring together all the amazing work you've been doing on motion tokens. Please feel free to suggest other options than the ones I've provided!

Related Links

johanstromqvist commented 2 years ago

Great summary, Laura!

Naming convention

Recommendation:

Rationale: Descriptive, familiar, consistent – great base line. We can get creative with patterns and aliases later on top of this.

Values

Recommendation:

Rationale:

*W3 CSS spec

Name Equivalent to
ease cubic-bezier(0.25, 0.1, 0.25, 1)
ease-in cubic-bezier(0.42, 0, 1, 1)
ease-out cubic-bezier(0, 0, 0.58, 1)
ease-in-out cubic-bezier(0.42, 0, 0.58, 1)
linear cubic-bezier(0, 0, 1, 1)
aaronccasanova commented 2 years ago

Duration: Increments of 1/60 between 0 and 500ms [0, 16, 33, 50, 66, 83, 100, 116, ..., 450, 466, 483, 500] Duration: An increment based on the render budget of a frame at 60 fps gives us a true duration primitive and a base line for education and informed decisions making.

@johanstromqvist Can you expand on the rationale here? What do you mean by gives us true duration primitives and baseline education? I'm not used to thinking in FPS but rather delta time. For example, the first thing I do when starting a canvas project is setup frame independent delta time to counter act varying refresh rates between devices.

Additionally, I took a quick look around at other design systems and none seem to factor FPS in their tokens or educational content:

Also, I'm wondering how that will affect communication. Since every developer is already familiar with the concept of time (e.g. seconds and milliseconds). What benefit do we get from saying I want X to animate from position A to B over 30 Frames, rather than I want X to animate from position A to B over half a second?

johanstromqvist commented 2 years ago

Great questions @aaronccasanova – love that you're scrutinizing this!

First of all, why do we need so many values? Well, there are plenty of use cases that need values in between the 50 increments (and especially in between the 100s). If we don't provide them, animations will either need to conform to the system or be implemented off the system. So the question is – what are the most useful sub 50 values?

true duration primitives

What I referred to as a "primitive" here is what I think is "the smallest meaningful part", i.e. in the browser whether we specify 1ms, 5ms, 10ms, or 15ms, all those values round up to one frame (16.667ms). E.g. if you write transition-duration: 175ms that's equivalent to 10.5 frames which means that 11 frames will render which is in fact 183ms. This assumes a refresh rate 60 fps, but it's a useful framework at other frame rates too. Thinking out loud:

baseline education

Frames connecting to fluid motion is the foundation of animation. Both designers and developers can make better decisions if they have a baseline understanding of the relationship between frames and UX, and frames and rendering.

From an Eng POV we need to understand concepts like frame budget, low performance and frame drops. And from a UX POV we need to understand why advanced animations need more frames to be clear, or that we sometimes need to simplify the animation concept to fit how many frames we can afford to spend. We have planned for a "smooth animations" project towards the end of 2022 where I think this narrative will be very helpful.

other design systems

Most systems do have values that fit this model as every increment of 50 equals 3 frames. Most of the time this is exactly what we need. 50ms, 100ms, 150ms, 300ms – check, check, great! The question is what we do between the 50s. Sometimes we need to tweak timing granularly. When we do, values like Adobe Spectrum's 190ms and 220ms are just inaccurate, as they will translate to 200ms and 233ms respectively. It's like fluff-specificity and adds a false sense of control. When you're looking for a visual result between the 50s, the only values that matter are 16, 33, 66 and 83.

Best case scenario, other systems look at ours and are inspired. Second best case, they look at ours point out why it's a total mess, and we learn and iterate. 😅

how it affects communication

We are definitely sticking to the concept of time. Durations are always expressed in ms and we will almost always talk about milliseconds and seconds. The exception is physics based animation which I anticipate will be increasingly common.

For the sake of simplicity and approachability, our docs, patterns and default values should focus on the 50s and 100s. This will fit 90% of the use cases. The remaining 10% deals with the challenge of granular tweaking and should probably be an "advanced" sub section for those who need it. That's where the 16, 33, 66 and 83s will be valuable, and it's still all milliseconds. We're just using frame rate to find the most relevant ones. If the added complexity turns out to outweigh the benefits, we'll just scrap it. But I have yet to find a better tradeoff.

alex-page commented 2 years ago

This work is complete. We can now create a new issue to iterate on the values and implementation. The tokens current tokens are in this file https://github.com/Shopify/polaris-react/blob/v8.0.0-major/src/tokens/_motion.ts

johanstromqvist commented 2 years ago

@alex-page Is there an issue for the value updates? Couldn't find one.

alex-page commented 2 years ago

Feel free to make an issue in the backlog. It will need design guidance so if you have opinions it would be great to start there.

lgriffee commented 2 years ago

Created a follow up issue for the future easing and duration token value updates! 👆🏻

johanstromqvist commented 2 years ago

Thanks Laura! I split the issue in two; one for duration, one for easing.