Why css transform properties are not separate css properties? - css

Why was decided to add all transform properties to one css property?
Instead of:
.element {
transform: translateX(50%) rotateZ(45deg) scale(0.8);
}
could be written:
.element {
translate-x: 50%;
rotate-z: 45deg;
scale: 0.8;
}
Why was chosen first variant? Is this somehow connected with transformation matrix? Or prefixes?
When you write media queries for example, you need to copy the entire transform property (not DRY), in other case, it will be overwritten. The same problem when creating transitions, toggling states, etc... That's really uncomfortable.
An explanation of what is happening behind the scene is welcome.

Because it's not just a set of properties, it's a list, where the transformations are applied in order, and a single transform type can occur more than once in the list.
So you can for example do:
div {
height:100px;
width: 100px;
}
.one {
background-color:red;
transform:translate(50%,5%) scale(0.5)
rotate(45deg) translate(50px,10px)
}
.two {
background-color:blue;
transform:scale(0.5) translate(50%,5%)
rotate(45deg) translate(50px,10px)
}
<div class="one"></div>
<div class="two"></div>
And you can see that the X-position of the diamond is affected by the order in which the transforms are applied.

Related

Wired Rotatation in Tailwind

The two blocks behave differently when applying tailwind's "rotate(**deg)" and vanilla css "transform: rotate(**deg)". Please just hover the blue blocks to reproduce.
https://play.tailwindcss.com/Rgf2GJ6mim
Since I sometimes use css in #layer utilities to write nested styles, so could someone please help me understand this? Big Thanks!!
Despite it looks like both examples do the same thing it's not quite true. Let's find out the difference. All classes in your example are same but the last one
hover:[transform:rotate(1020deg)] generates this
.hover\:\[transform\:rotate\(1020deg\)\]:hover {
transform: rotate(1020deg);
}
while hover:rotate-[1020deg] this
.hover\:rotate-\[1020deg\]:hover {
--tw-rotate: 1020deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
Or if you fill in Tailwind variables with its values it all comes to comparison between
.hover\:\[transform\:rotate\(1020deg\)\]:hover {
transform: rotate(1020deg);
}
// and
.hover\:rotate-\[1020deg\]:hover {
transform: translate(0, 0) rotate(1020deg) skewX(0) skewY(0) scaleX(1) scaleY(1);
}
We're forgot about one VERY important class - rotate-0. It actually sets the starting point of CSS transition
.rotate-0 {
--tw-rotate: 0deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
Just remove rotate-0 from both of your examples and now there is no difference in transition. So what is happening?
It all comes in CSS transition from state 1 to state 2. (Let's remove last
parts with skew and scale)
First example - from translate(0, 0) rotate(0deg) to rotate(1020deg)
Second - from translate(0, 0) rotate(0deg) to
translate(0, 0) rotate(1020deg)
MDN says
The transform functions are multiplied in order from left to right, meaning that composite transforms are effectively applied in order from right to left.
See example: red square just rotating. Yellow - rotates but returns back to default position even on hover we do NOT changing translate property. We're assuming it will left the same but this is not how CSS transition works. When there are multiple transform occurrence the last one will override previous. That's why translate is not applied anymore on hover - we're "erasing" it. In order to fix it we need to keep translate on hover (blue example)
.example {
width: 60px;
height: 60px;
margin: 40px;
transition: 1000ms;
}
.example-1 {
background-color: red;
transform: rotate(0);
}
.example-2 {
background-color: yellowgreen;
transform: translate(100px) rotate(0deg);
}
.example-3 {
background-color: blue;
transform: translate(100px) rotate(0);
}
.example-1:hover {
transform: rotate(45deg);
}
.example-2:hover {
transform: rotate(45deg);
}
.example-3:hover {
background-color: blue;
transform: translate(100px) rotate(45deg);
}
<div class="example example-1"></div>
<div class="example example-2"></div>
<div class="example example-3"></div>
And that's exactly what happening in your example - you are missing translate function in compiled CSS and changing the default state of transformed object (it is not transitioning anymore - it just places the new state). We need to keep the order of the chaining functions in transform property to ensure everything will work as expected
So, few ways to fix it in Tailwind keeping initial state (rotate-0 class), both requires to change hover:[transform:rotate(1020deg)] class
First - add missing translate function - change class into hover:[transform:translate(0,0)_rotate(1020deg)]
Second - not so obvious - change --tw-rotate variable value, basically convert class into hover:[--tw-rotate:1020deg]
And finally as I said - just remove initial state (rotate-0) but sometimes it is not an option
See examples
It's not the best explanation but I tried to point you in some direction where the difference comes from

CSS transform: How can I make transform value a combination of two different classes? [duplicate]

I have an element with two classes, one called "rotate" that will rotate the element 360 degrees and another called "doublesize" that will scale the element 2x its normal size:
.rotate {
transform: rotate(0deg);
}
.rotate:hover {
transform: rotate(360deg);
}
.doublesize {
transform: scale(1);
}
.doublesize:hover {
transform: scale(2);
}
http://jsfiddle.net/Sbw8W/
I'm guessing this does not work because the classes override each other's transform property?
I know that I could easily do this in one CSS rule like:
.doublerotatesize {
transform: scale(1) rotate(0deg);
}
.doublerotatesize:hover {
transform: scale(2) rotate(360deg);
}
But I would like to be able to apply each class separately from the other if it is possible.
I'm guessing this does not work because the classes override each other's transform property?
Correct. This is an unfortunate limitation as a side-effect of how the cascade works.
You will have to specify both functions in a single transform declaration. You could simply chain both class selectors together instead of creating a new class for a combined transform:
.doublesize.rotate {
-webkit-transform: scale(1) rotate(0deg);
}
.doublesize.rotate:hover {
-webkit-transform: scale(2) rotate(360deg);
}
... but as you can see, the issue lies in the transform property rather than in the selector.
This is expected to be rectified in Transforms level 2, where each transform has been promoted to its own property, which would allow you to combine transforms simply by declaring them separately as you would any other combination of CSS properties. This means you would be able to simply do this:
/* Note that rotate: 0deg and scale: 1 are omitted
as they're the initial values */
.rotate:hover {
rotate: 360deg;
}
.doublesize:hover {
scale: 2;
}
... and take advantage of the cascade rather than be hindered by it. No need for specialized class names or combined CSS rules.
Using CSS variables you can have this separation. The idea is to chain as many transformation as you want inside the element using CSS variables then later you update each variable individually:
div {
width: 100px;
height: 100px;
display: inline-block;
background-color: red;
transform:
/* I prepared 3 placeholder so we can chain 3 transformation later */
var(--t1,) /* we need "nothing" as fallbak*/
var(--t2,)
var(--t3,);
}
.transitionease {
transition: all 1s ease;
}
.transitionease:hover {
transition: all 3s ease;
}
.rotate {
--t1: rotate(0deg);
}
.rotate:hover {
--t1: rotate(360deg);
}
.doublesize {
--t2: scale(1);
}
.doublesize:hover {
--t2: scale(2);
}
<div class="transitionease"></div>
<div class="transitionease doublesize rotate "></div>
<div class="transitionease doublesize"></div>
You can surely combine multiple animation, but not by combining CSS classes. See the first answer here : combining multiple css animations into one overall animation
The first part tells you how to combine animation with CSS with some parameters (delay, duration) :
.outside.animate {
-webkit-animation-delay: 0s, .5s, .5s;
-webkit-animation-duration: 500ms, 1000ms, 1000ms;
-webkit-animation-name: button-bounce, rotate, skyblue;
}
For sure, you firstly need to define your animations.
You can't do this using just CSS, but if you don't mind using some jQuery you can attach this effect:
var $elem = $('.cssDropPinImage');
$({deg: 0}).animate({deg: 360}, {
duration: 600,
step: function(now) {
var scale = (2 * now / 360);
$elem.css({
transform: 'rotate(' + now + 'deg) scale(' + scale + ')'
});
}
});
body {
padding: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img src="https://via.placeholder.com/40" class="cssDropPinImage">
The question hints at a scale and rotate animation on hover. Another pragmatic way of solving this, is by rearranging the elements in the HTML and applying the individual transforms to the parent and the child, only responding to the hover on the parent element.
That looks something like this:
<div class="doublesize">
<div class="rotate"></div>
</div>
.doublesize {
transition: transform 1s ease;
}
.rotate {
width: 100px;
height: 100px;
background-color: red;
transition: transform 1s ease;
}
.doublesize:hover {
transform: scale(2);
}
.doublesize:hover .rotate {
transform: rotate(360deg);
}
http://jsfiddle.net/n38csxy1/4/
I know this is quite a leap from the original setup, but I hope this perspective helps someone nevertheless.

Transform on svg text element does not move the element [duplicate]

Using CSS, how can I apply more than one transform?
Example: In the following, only the translation is applied, not the rotation.
li:nth-child(2) {
transform: rotate(15deg);
transform: translate(-20px,0px);
}
You have to put them on one line like this:
li:nth-child(2) {
transform: rotate(15deg) translate(-20px,0px);
}
When you have multiple transform directives, only the last one will be applied. It's like any other CSS rule.
Keep in mind multiple transform one line directives are applied from right to left.
This: transform: scale(1,1.5) rotate(90deg);
and: transform: rotate(90deg) scale(1,1.5);
will not produce the same result:
.orderOne, .orderTwo {
font-family: sans-serif;
font-size: 22px;
color: #000;
display: inline-block;
}
.orderOne {
transform: scale(1, 1.5) rotate(90deg);
}
.orderTwo {
transform: rotate(90deg) scale(1, 1.5);
}
<div class="orderOne">
A
</div>
<div class="orderTwo">
A
</div>
I'm adding this answer not because it's likely to be helpful but just because it's true.
In addition to using the existing answers explaining how to make more than one translation by chaining them, you can also construct the 4x4 matrix yourself
I grabbed the following image from some random site I found while googling which shows rotational matrices:
Rotation around x axis:
Rotation around y axis:
Rotation around z axis:
I couldn't find a good example of translation, so assuming I remember/understand it right, translation:
[1 0 0 0]
[0 1 0 0]
[0 0 1 0]
[x y z 1]
See more at the Wikipedia article on transformation as well as the Pragamatic CSS3 tutorial which explains it rather well. Another guide I found which explains arbitrary rotation matrices is Egon Rath's notes on matrices
Matrix multiplication works between these 4x4 matrices of course, so to perform a rotation followed by a translation, you make the appropriate rotation matrix and multiply it by the translation matrix.
This can give you a bit more freedom to get it just right, and will also make it pretty much completely impossible for anyone to understand what it's doing, including you in five minutes.
But, you know, it works.
Edit: I just realized that I missed mentioning probably the most important and practical use of this, which is to incrementally create complex 3D transformations via JavaScript, where things will make a bit more sense.
You can also apply multiple transforms using an extra layer of markup e.g.:
<h3 class="rotated-heading">
<span class="scaled-up">Hey!</span>
</h3>
<style type="text/css">
.rotated-heading
{
transform: rotate(10deg);
}
.scaled-up
{
transform: scale(1.5);
}
</style>
This can be really useful when animating elements with transforms using Javascript.
You can apply more than one transform like this:
li:nth-of-type(2){
transform : translate(-20px, 0px) rotate(15deg);
}
Some time in the future, (now available, see updates below) we can write it like this:
li:nth-child(2) {
rotate: 15deg;
translate:-20px 0px;
}
This will become especially useful when applying individual classes on an element:
<div class="teaser important"></div>
.teaser{rotate:10deg;}
.important{scale:1.5 1.5;}
This syntax is defined in the in-progress CSS Transforms Level 2 specification, but can't find anything about current browser support other then chrome canary. Hope some day i'll come back and update browser support here ;)
Found the info in this article which you might want to check out regarding workarounds for current browsers.
UPDATE: feature has landed in firefox 72
UPDATE: now available in chrome 104, edge 104, safari 14.1, see https://web.dev/css-individual-transform-properties/#:~:text=support%20these%20properties.-,Browser%20support,-%3A
Lesson I`ve learnt.
If you are using style's React css, don't include semicolons, even in the end of it because it is included automatically and internally by React.
Just like:
style={
transform: "rotate(90deg) scaleX(-1)",
}
It's an old question but turned out to be very relevant to me recently. I found 'transform: matrix(..)' more convenient to use.
So for the issue in question, the below example applies ~15deg rotation and translation of 20px to the right (for convenience) on the x-axis.
.matrixTransform {
transform-origin: top left;
transform: matrix(0.965,0.258,-0.258,0.965,20,0);
font-size: 30px;
}
.combinedTransform {
transform-origin: top left;
transform: rotate(15deg) translateX(20px);
font-size: 30px;
}
<div class=matrixTransform>A</div>
<div class=combinedTransform>B</div>
From what I gather this particular transform uses initial 4 parameters to represent transformations e.g. scale, rotate. And seem to be in rows by columns order.
The remaining two are translations in the x and y axis respectively.
I found Wolfram Alpha cheat sheet (calculator) very useful.
Just start from there that in CSS, if you repeat 2 values or more, always last one gets applied, unless using !important tag, but at the same time avoid using !important as much as you can, so in your case that's the problem, so the second transform override the first one in this case...
So how you can do what you want then?...
Don't worry, transform accepts multiple values at the same time... So this code below will work:
li:nth-child(2) {
transform: rotate(15deg) translate(-20px, 0px); //multiple
}
If you like to play around with transform run the iframe from MDN below:
<iframe src="https://interactive-examples.mdn.mozilla.net/pages/css/transform.html" class="interactive " width="100%" frameborder="0" height="250"></iframe>
Look at the link below for more info:
<< CSS transform >>
Transform Rotate and Translate in single line css:-How?
div.className{
transform : rotate(270deg) translate(-50%, 0);
-webkit-transform: rotate(270deg) translate(-50%, -50%);
-moz-transform: rotate(270deg) translate(-50%, -50%);
-ms-transform: rotate(270deg) translate(-50%, -50%);
-o-transform: rotate(270deg) translate(-50%, -50%);
float:left;
position:absolute;
top:50%;
left:50%;
}
<html>
<head>
</head>
<body>
<div class="className">
<span style="font-size:50px">A</span>
</div>
</body>
</html>

How can I apply multiple transform declarations to one element?

I have an element with two classes, one called "rotate" that will rotate the element 360 degrees and another called "doublesize" that will scale the element 2x its normal size:
.rotate {
transform: rotate(0deg);
}
.rotate:hover {
transform: rotate(360deg);
}
.doublesize {
transform: scale(1);
}
.doublesize:hover {
transform: scale(2);
}
http://jsfiddle.net/Sbw8W/
I'm guessing this does not work because the classes override each other's transform property?
I know that I could easily do this in one CSS rule like:
.doublerotatesize {
transform: scale(1) rotate(0deg);
}
.doublerotatesize:hover {
transform: scale(2) rotate(360deg);
}
But I would like to be able to apply each class separately from the other if it is possible.
I'm guessing this does not work because the classes override each other's transform property?
Correct. This is an unfortunate limitation as a side-effect of how the cascade works.
You will have to specify both functions in a single transform declaration. You could simply chain both class selectors together instead of creating a new class for a combined transform:
.doublesize.rotate {
-webkit-transform: scale(1) rotate(0deg);
}
.doublesize.rotate:hover {
-webkit-transform: scale(2) rotate(360deg);
}
... but as you can see, the issue lies in the transform property rather than in the selector.
This is expected to be rectified in Transforms level 2, where each transform has been promoted to its own property, which would allow you to combine transforms simply by declaring them separately as you would any other combination of CSS properties. This means you would be able to simply do this:
/* Note that rotate: 0deg and scale: 1 are omitted
as they're the initial values */
.rotate:hover {
rotate: 360deg;
}
.doublesize:hover {
scale: 2;
}
... and take advantage of the cascade rather than be hindered by it. No need for specialized class names or combined CSS rules.
Using CSS variables you can have this separation. The idea is to chain as many transformation as you want inside the element using CSS variables then later you update each variable individually:
div {
width: 100px;
height: 100px;
display: inline-block;
background-color: red;
transform:
/* I prepared 3 placeholder so we can chain 3 transformation later */
var(--t1,) /* we need "nothing" as fallbak*/
var(--t2,)
var(--t3,);
}
.transitionease {
transition: all 1s ease;
}
.transitionease:hover {
transition: all 3s ease;
}
.rotate {
--t1: rotate(0deg);
}
.rotate:hover {
--t1: rotate(360deg);
}
.doublesize {
--t2: scale(1);
}
.doublesize:hover {
--t2: scale(2);
}
<div class="transitionease"></div>
<div class="transitionease doublesize rotate "></div>
<div class="transitionease doublesize"></div>
You can surely combine multiple animation, but not by combining CSS classes. See the first answer here : combining multiple css animations into one overall animation
The first part tells you how to combine animation with CSS with some parameters (delay, duration) :
.outside.animate {
-webkit-animation-delay: 0s, .5s, .5s;
-webkit-animation-duration: 500ms, 1000ms, 1000ms;
-webkit-animation-name: button-bounce, rotate, skyblue;
}
For sure, you firstly need to define your animations.
You can't do this using just CSS, but if you don't mind using some jQuery you can attach this effect:
var $elem = $('.cssDropPinImage');
$({deg: 0}).animate({deg: 360}, {
duration: 600,
step: function(now) {
var scale = (2 * now / 360);
$elem.css({
transform: 'rotate(' + now + 'deg) scale(' + scale + ')'
});
}
});
body {
padding: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img src="https://via.placeholder.com/40" class="cssDropPinImage">
The question hints at a scale and rotate animation on hover. Another pragmatic way of solving this, is by rearranging the elements in the HTML and applying the individual transforms to the parent and the child, only responding to the hover on the parent element.
That looks something like this:
<div class="doublesize">
<div class="rotate"></div>
</div>
.doublesize {
transition: transform 1s ease;
}
.rotate {
width: 100px;
height: 100px;
background-color: red;
transition: transform 1s ease;
}
.doublesize:hover {
transform: scale(2);
}
.doublesize:hover .rotate {
transform: rotate(360deg);
}
http://jsfiddle.net/n38csxy1/4/
I know this is quite a leap from the original setup, but I hope this perspective helps someone nevertheless.

css transform without overwriting previous transform?

I have an element:
elem
transform translateY(5px) scale(1.2)
Now on hover I want it to move down an additional 5px
elem:hover
transform translateY(5px)
Obviously this would overwrite the previous transform. Is there anyway to set it to move an additional 5 without knowing what the previous transform state is?
Thanks.
CSS custom properties aka CSS variables are the only answer to this (outside of using Javascript).
To add to a previous value:
div {
--translateX: 140;
--trans: calc(var(--translateX) * 1px);
transform: translateX(var(--trans)) scale(1.5);
}
div:hover {
--translatemore: calc(var(--translateX) + 25);
--trans: calc(var(--translatemore) * 1px);
}
div {
transition: .2s transform;
width: 50px;
height: 50px;
background: salmon;
}
All browsers now support the ability to set transform properties individually.
https://caniuse.com/mdn-css_properties_translate
https://caniuse.com/mdn-css_properties_scale
Use the WebKitCSSMatrix object or MozCSSMatrix (I think...) to set new values trough the original object without knowing the initial transform.
http://jsfiddle.net/Cx9hH/
In this case I have an initial translate of 100px on witch I add an extra 100px:
box.style.webkitTransform = matrix.translate(100, 0);
You just have to create another div or span wrapper and set transform to that intead
You could use nested elements, and increase the translation on each on interdependently for each variable.
Although, that would get ugly pretty quick.
CSS variables!
https://jsfiddle.net/91q0s5h0/4/
div {
--translateX: 10px;
transform: translateX(var(--translateX)) scale(1.5);
}
div:hover {
--translateX: 5px;
}

Resources