-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
3D transform utilities #13248
Conversation
There was a problem hiding this 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.
9a22baa
to
1e012ff
Compare
We want to avoid triggerring unnecessary 3D transformations.
9361537
to
ef72110
Compare
|
||
/** | ||
* @css `scale` | ||
*/ | ||
functionalUtility('scale-y', { | ||
functionalUtility('scale-3d', { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)]
.
There was a problem hiding this comment.
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()
)?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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>'), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)]
).
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- The order of rotations is explicitly specified.
- It's clear the
transform
property is being modified, rather thanrotate
.
There are also a few reasons not to translate multiple rotations into a vector:
- CSS variables can't be supported if the translation is done in code at compile time.
- Implementing the translation with CSS math functions is technically possible, but ridiculously long and likely slow.
- Any translation would need to assume an order of composition, unless that was also configurable, ballooning the complexity.
- 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!
There was a problem hiding this comment.
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!
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.
2e302b1
to
d5a2f47
Compare
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.
|
4278054
to
4364362
Compare
There was a problem hiding this 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
Based on #10982 by @brandonmcconnell.
Add 3D transform utilities. All are applied to specific CSS properties rather than as
transform
functions.rotate
rotate
(e.g.
rotate_[1_2_3_45deg]
)rotate-x
rotate
rotate
, applied in the x-dimension; see note belowrotate-y
rotate
rotate
, applied in the y-dimension; see note belowscale
scale
(e.g.
scale-[2_1.5_3]
,scale-[var(--custom-scale)]
)scale-z
scale
scale
, applied only to the z-dimensionscale-3d
scale
scale
across the z-dimension(e.g.
scale-50 scale-3d
is equivalent toscale-50 scale-z-50
)translate-z
translate
translate
, applied only to the z-dimensiontranslate-3d
translate
translate
across the z-dimension(e.g.
translate-full translate-3d
is equivalent totranslate-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
origin
(transform-origin
)transform
transform
transform-[rotateX(45deg)_rotateY(-90deg)]
) are now supportedtransform-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
, androtate-y
: Rotations are applied using therotate
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 arbitrarytransform
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.