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

3D transform utilities #13248

Merged
merged 32 commits into from
Mar 19, 2024
Merged

3D transform utilities #13248

merged 32 commits into from
Mar 19, 2024

Conversation

KrisBraun
Copy link
Contributor

@KrisBraun KrisBraun commented Mar 14, 2024

Based on #10982 by @brandonmcconnell.

Add 3D transform utilities. All are applied to specific CSS properties rather than as transform functions.

Utility CSS Property CSS Value
rotate rotate arbitrary values can be used to define an axis of rotation
(e.g. rotate_[1_2_3_45deg])
rotate-x rotate same as rotate, applied in the x-dimension; see note below
rotate-y rotate same as rotate, applied in the y-dimension; see note below
scale scale arbitrary values are now supported
(e.g. scale-[2_1.5_3], scale-[var(--custom-scale)])
scale-z scale same as scale, applied only to the z-dimension
scale-3d scale applies scale across the z-dimension
(e.g. scale-50 scale-3d is equivalent to scale-50 scale-z-50)
translate-z translate same as translate, applied only to the z-dimension
translate-3d translate applies translate across the z-dimension
(e.g. translate-full translate-3d is equivalent to translate-full translate-z-full)
perspective perspective dramatic (100px), near (300px), normal (500px), midrange (800px), distant (1200px), or arbitrary (e.g. perspective-[42px])
perspective-origin perspective-origin same as origin (transform-origin)
transform transform arbitrary values (e.g. transform-[rotateX(45deg)_rotateY(-90deg)]) are now supported
transform-flat transform-style flat
transform-3d transform-style preserve-3d
transform-content transform-box content-box
transform-border transform-box border-box
transform-fill transform-box fill-box
transform-stroke transform-box stroke-box
transform-view transform-box view-box
backface-visible backface-visibility visible
backface-hidden backface-visibility hidden

Note on rotate, rotate-x, and rotate-y: Rotations are applied using the rotate property, which supports a single angle of rotation along with an axis of rotation. rotate defaults to the z-dimension and can specify other dimensions (including a vector) using an arbitrary value. Applying multiple rotations in a sequence is order-dependent and can be done using an arbitrary transform value (e.g. transform-[rotateX(45deg)_rotateY(-90deg)]).

Care has been taken to only apply transformations in the z-dimension when the relevant utilities are used. This should avoid triggering GPU rendering except when needed.

@KrisBraun KrisBraun changed the title 3D rotation utilities 3D transform utilities Mar 14, 2024
Copy link
Member

@RobinMalfait RobinMalfait left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is still in draft mode, but saw it and wanted to comment already before I forgot.

packages/tailwindcss/src/utilities.ts Outdated Show resolved Hide resolved
packages/tailwindcss/src/utilities.ts Outdated Show resolved Hide resolved
@KrisBraun KrisBraun added the v4 label Mar 15, 2024
@KrisBraun KrisBraun marked this pull request as ready for review March 15, 2024 18:07

/**
* @css `scale`
*/
functionalUtility('scale-y', {
functionalUtility('scale-3d', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's a way to make this a static utility that acts like a flag instead of a new suite of classes? APIs like scale-3d-150 feel kinda yuck to me.

If we could do scale-150 scale-3d I think that might feel nicer, might have to think through what scale-x-150 scale-3d should mean but I think it feels solveable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's much better! Implemented here.

Effectively, scale sets the scale in all three dimensions, but only applies it in two. scale-3d applies across all three. scale-x, scale-y, and scale-z can be used to set (or override) the scale in individual dimensions.

In the case of scale-x-150 scale-3d, the scale-3d has no effect, as the scale in the z-dimension is 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scale-z also applies in three dimensions, so scale-3d is not required alongside scale-z.

Copy link
Contributor

@brandonmcconnell brandonmcconnell Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we still support a way for 3d transforms to be made in a single utility, like scale3d-[2,1.5,3]

Especially for something that gets transitioned as often as transform properties, it would be useful to be able to interpret these values onto element and reference them via a single var.

<div
  style={{
    '--scale3d': scale3d
  }}
  class="scale3d-[--scale3d]"
/>

Without this, we could still interpret values this way, but we would have to break each axis for each transformation into its own CSS variable and connected tailwind utility.

<div
  style={{
    '--scale-x': scaleX,
    '--scale-y': scaleY,
    '--scale-z': scaleZ,
  }}
  class="scale-x-[--scale-x] scale-y-[--scale-y] scale-z-[--scale-z] scale-3d"
/>

Once you factor in other 3d transformations like rotations and translations, this could become a very large implementation, since each brings its own 6 lines of implementation.

I think, as long as the below still works, we can just use more complex 3d situations like this when needed:

<div
  style={{
    '--scale3d': scale3d
  }}
  class="[scale3d:var(--scale3d)]"
/>

I recognize that practically, we don't do these sorts of transformations every day, so in a few circumstances where we need them, using entire arbitrary values or interpolating on each property/axis pair isn't so unreasonable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, an escape hatch for arbitrary values is helpful. I've added that so your first example can be written scale-[2_1.5_3] and the last can be simplified to scale-[var(--scale3d)].

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I presume scale-[--scale3d] would also work (without var())?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will support whatever Tailwind CSS v4 supports in general. Bare variables lead to a number of parsing ambiguities, so there's a chance support for them could be deprecated/dropped, but no decisions have been made yet.

--perspective-near: 300px;
--perspective-normal: 500px;
--perspective-midrange: 800px;
--perspective-distant: 1200px;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense that we need to add these to the theme if we want a named scale but also a bummer because we worked so hard to cut back on the size of the default theme. Anyone have thoughts on just making this purely numeric/bare-value based? I'm like 75% for named, 25% for numeric but curious what others think. Admittedly this is a really good example of a property where people would benefit for a small curated set of options, because nobody knows what the hell these numbers mean.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the named scale as well. It's conceptually distance from the screen so bare values seems fine to me too but I think a small, default set of values is more beneficial.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on our discussion in #10982, I also think this small set of named values would be very helpful here, plus allowing for numeric in case someone wants to do perspective-[3000] (or perspective-3000 if you're dropping brackets as part of v4).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @brandonmcconnell, that helped me spot that we weren't supporting bare values (e.g. perspective-123), just arbitrary values (e.g. perspective-[123px]). Both are now supported.

// Vector components for the axis of rotation
property('--tw-rotate-x', '0', '<number>'),
property('--tw-rotate-y', '0', '<number>'),
property('--tw-rotate-z', '0', '<number>'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my head I was thinking we'd just have two variables:

property('--tw-rotate', '0deg', '<angle>'),
property('--tw-rotate-axis'),

Then the values for --tw-rotate-axis would be something like:

[x | y | z | <number{3}]

...however you do that with syntax in @property.

This means you couldn't do this:

<div class="rotate-45 rotate-x rotate-y">

...but I think that doesn't bother me personally, you'd just have to use a vector for that:

<div class="rotate-45 rotate-[1_1_0]">

I'm not sure how common it actually is to want to rotate something by exactly the same angle on two different axis — my gut is probably not common?

With this change we wouldn't have to translate rotate-x to a vector, it could just set --tw-rotate-axis: x because rotate: x 45deg is valid.

Copy link
Contributor

@thecrypticace thecrypticace Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add to this — we could (later) add rotate-xy as a utility too if we really wanted. Which seems just as explanatory as two separate utilities. So I'm Adam on this one. A single property for the axis feels like a good way to do this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we still be able to do something like rotate-x-45 rotate-y-135 rotate-z-20?

Having granular control over individual axis transforms in a pragmatic way rather than using a vector is an important feature here IMO.

This is possible, and can even be converted into a vector under the hood if need be, which I demo'd here:
https://x.com/branmcconnell/status/1768009776938844550

Copy link
Contributor Author

@KrisBraun KrisBraun Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've further simplified this by specifying the rotation axis via a modifier on rotate:

Class CSS
rotate-45 rotate: 45deg
rotate-45/x rotate: x 45deg
rotate-45/y rotate: y 45deg
rotate-45/z rotate: z 45deg
rotate-45/[1_2_3] rotate: 1 2 3 45deg

This makes it clear angles are not composable, in line with what CSS supports via the rotate property.

Composing multiple angles requires a pipeline using transform. That is now supported via arbitrary transform values (e.g. transform-[rotateX(45deg)_rotateY(-90deg)]).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrisBraun Yes, I was primarily speaking about using multi-axis rotation using the traditional syntax instead of the vector syntax.

This is possible using even the regular rotate properties if you convert them to vector syntax under the hood. I demonstrate this in the twitter thread I linked above (also here).

So hypothetically, someone could do this and get the expected behavior:

rotate-45/x rotate-30/y rotate-60/z

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After more thought, we decided rotate-x and rotate-y are more in line with other Tailwind utilities, even if they are mutually exclusive. The PR and description have been updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brandonmcconnell Totally understand the appeal of composable rotation angles. They're achievable using an arbitrary transform value as shown above. It's more verbose but has at least a couple advantages:

  1. The order of rotations is explicitly specified.
  2. It's clear the transform property is being modified, rather than rotate.

There are also a few reasons not to translate multiple rotations into a vector:

  1. CSS variables can't be supported if the translation is done in code at compile time.
  2. Implementing the translation with CSS math functions is technically possible, but ridiculously long and likely slow.
  3. Any translation would need to assume an order of composition, unless that was also configurable, ballooning the complexity.
  4. The translated values would be harder to understand in dev tools.

We're going with the grain of CSS on this. The rotate property is clearly designed for applying a single angle of rotation, and transform is the escape hatch for more complex transformation pipelines.

Thanks so much for initiating this PR! We're now at a point where the simple things are simple and everything else is possible. We're looking forward to seeing what folks build with this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally on board with this. Thanks, @KrisBraun!

@MoARABY
Copy link

MoARABY commented Mar 17, 2024

it will be useful property

Support `perspective-123` (but not `perspective-potato`)
Instead of scale-3d taking a separate scale, it modifies scale to apply in three dimensions.
Support arbitrary value for scale (e.g. `scale-[1_2_3.5]`).
Support single rotation angles in line with the [CSS `rotate` property](https://developer.mozilla.org/en-US/docs/Web/CSS/rotate). Using modifiers (e.g. `rotate-45/x`) makes it clearer that the axis of rotation is modified. Thanks @adamwathan for this suggestion.

Composing angles is only supported in CSS via a pipeline of `transform` functions. I'll add arbitrary value support to `transform` next as an escape hatch for those cases that need more complex transformations.
Support arbitrary values for `transform`. The `skew-x` and `skew-y` transforms are applied before any arbitrary transformations.
Both work the same way as scale-z and scale-3d.
@KrisBraun
Copy link
Contributor Author

translate-z and translate-3d have been added. They work similarly to scale and scale-3d. (Also, missing translate-[xyz]-px utilities were added.)

Copy link
Member

@RobinMalfait RobinMalfait left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, a few questions remaining

packages/tailwindcss/src/utilities.test.ts Outdated Show resolved Hide resolved
packages/tailwindcss/src/utilities.test.ts Outdated Show resolved Hide resolved
packages/tailwindcss/src/utilities.test.ts Show resolved Hide resolved
packages/tailwindcss/src/utilities.test.ts Show resolved Hide resolved
packages/tailwindcss/src/utilities.ts Outdated Show resolved Hide resolved
@RobinMalfait RobinMalfait merged commit dadb096 into next Mar 19, 2024
1 check passed
@RobinMalfait RobinMalfait deleted the feat/3d-transform-utilities branch March 19, 2024 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants