I have a problem where CSS Transform Rotate generates a new element and rotates it overlapping the actual one when I dynamically add the class to the element.
And if I set default rotate(deg) to element. The animation works fine.
I have no idea whether this is a feature or issue of CSS.
I build this element in Electron by ReactJS. Is it related to WebKit of Electron?
.parent-class > .btn-demo {
background: url(image.png) no-repeat center center;
transition: all .3s ease-in-out;
// if add default degree then it works fine
// transform: rotate(0deg);
}
.parent-class.animated > .btn-demo {
// it will copy a new element and overlap real one
transform: rotate(45deg);
}
Related
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
I have a less file that hide and display an element like the following:
.cmp-accordion__panel {
&--hidden {
display: none;
}
&--expanded {
display: block;
-webkit-animation: slide-down 0.5s ease-out;
-moz-animation: slide-down 0.5s ease-out;
}
}
#-webkit-keyframes slide-down {
0% {
opacity: 0;
-webkit-transform: translateY(-5%);
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
}
}
#-moz-keyframes slide-down {
0% {
opacity: 0;
-moz-transform: translateY(-5%);
}
100% {
opacity: 1;
-moz-transform: translateY(0);
}
}
In my JavaScript, I toggle the class name of the element between "cmp-accordion__panel--hidden" and "cmp-accordion__panel--expanded" if the event is triggered. I use keyframe and opacity to animate the transition from "display:none" to "display:block".
However, when I go from "display:block" to "display:none" to hide the element, the effect happens INSTANTLY. What should I add to animate the hiding?
As already said, is not possible animate or transition from display:block; to display: none; but this could be simulated in another way and is not necessary to use CSS animations, simply CSS transitions (in addition, is not necessary anymore to use vendor-prefixes to declare transitions or animations).
Please, look at this working example:
HTML (I inserted a fake content to create an element with a relative big height)
<div class="cmp-accordion__panel--expanded">
b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b<br>b
</div>
LESS
[class*="cmp-accordion__panel"] {
border:solid 1px red;
overflow:hidden;
transition:opacity 0.3s ease-out, max-height 0.8s ease-out;
}
.cmp-accordion__panel {
&--hidden {
max-height:0;
opacity:0;
}
&--expanded {
opacity:1;
max-height:1000px;
}
}
Please note that, thanks to attribute partial value selector I added also some rules that apply to both *--hidden and *--expanded classes (I personally prefer a general class and an addition of a second one in some cases, instead of switching between two, but I did not want to change too much your approach).
The key rule is switching between two values of max-height property, from a 0 value to another "enough big" one. If you effectively know final height of the element you can simply use also height property, but in case of dynamic content, max-height did the trick.
Please note also the presence of overflow:hidden; applied to both classes, to simulate height changes.
Finally, animation effect relies only on a CSS transition applied to opacity and max-height properties, with different timings to enhance effect.
You cannot animate or transition from display: block; to display: none;, so you will need to remove this if you wish to animate it.
To ensure it fades and is removed you should animate the visibilty and opacity attributes.
Alternatively if you are using jQuery you can use the .fadeOut() function.
MDN - CSS Visibility
jQuery - fadeOut()
I'm working on CSS and web development,but just face a something which i really don't understand it:
.header{
position: absolute;
width:60%;
top: 20%;
left: 50%;
transform: translateX(-50%);<------ executed after animation
text-align: center;
animation: moveUp 2s;
animation-fill-mode: backwards;
}
#keyframes moveUp{
0%{
opacity: 0;
transform:translateY(2rem);
}
100%{
opacity: 1;
transform: translateY(0rem);
}
}
so my problem here is the indicated line doesn't apply on ".header" until the animation gets applied in other word it applies animation first then translate -50% ,is there a priority of execution here or it is different thing?
Usually the styles are parsed from top to bottom, however this isn't the issue here.
What is happening in your case is the transform is being applied initially, but then it is being overridden by the animation. Once the animation is over, the element is reverting back to its default style which has the transform.
Essentially, even though the transform is applied at first, you don't see it until the element reverts to it after the end of the animation.
The only solution if you want to have the transform during the animation, is to include it in the animation itself.
#keyframes moveUp {
0 % {
opacity: 0;
transform: translate(-50%, 2rem);
}
100 % {
opacity: 1;
transform: translate(-50%, 0);
}
}
EDIT: To clarify, the order at which the styles are applied does not matter. Whether the animation or the transform is applied first, the result will be the same.
I think a source of your confusion is that the first transform is a translateX while the animation only does translateY. In both cases what is changing is the value of the transform property of the element. Therefore which axis the translation is on doesn't matter. First you set transform: translateX(-50%), but then once the animation kicks in, transform becomes translateY(2rem). The translateX part is removed from the transform, unless you include it in the animation like I have shown.
I'm trying to combine several parts of animation together by clicking a button. Here's an example:
.element {
background-color: black;
display: block;
width: 160px;
height: 160px;
border-radius: 80%;
}
.one {
animation: one 1.5s ease 1 forwards;
}
.two {
animation: two 1s forwards;
}
#keyframes one {
from {
transform: scale(0.25);
opacity: 0;
}
25% {
opacity: 0.5;
}
to {
transform: scale(1);
opacity: 0.5;
}
}
#keyframes two {
from {
opacity: 0.5;
}
to {
opacity: 0;
}
}
I'm trying to combine these two animation: one and two. My way of doing this was to use JS: classList.add('.two') when I clicked the button. But the problem was: at the moment I added the class, the element changed to its default opacity which was 1.
To solve this, I added a new class contained styles which were actually clones of final styles of the first animation. And after the second part was finished, I had to remove the class list to prepared for the first animation to be played.
So my question is, is there a better way of doing this?
Here is a CodePen Demo
I just realised a problem with this: If I start the second animation before the first one was finished, there would be a discontinuity (the circle would just turns to a larger one all of a sudden).
The demo can be found from the above link, thanks!
Can I combine these two animations?
I assume by combine you mean producing forward (on click of add animation) and reverse (on click of remove animation) animations using the same keyframe rules. It's possible to achieve but for that both the forward and reverse animations should be exactly the same (but in opposite directions). When it is same, we can use animation-direction: reverse to achieve reverse effect with same keyframes.
Here, the forward animation has a transform change whereas the reverse doesn't and hence adding animation-direction: reverse would not produce the same effect as the original snippet. Moreover, coding it is not as easy as just adding a property also, a lot of work is needed like mentioned here.
What is the reason for the other two issues?
The reason for both the issues (that is, the element getting opacity: 1 immediately when the remove button is clicked and element getting full size when remove button is clicked while forward animation is still happening) are the same. When you remove the animation on an element (by removing the class) it immediately snaps to the size specified outside of the animation.
For the first case, the size is the one that is mentioned under .element (as .one is removed) and its opacity is default 1 because there is no opacity setting in it. For the second case, when the .one is removed and .two is added, the animation is removed and so the element's size is as specified in .element and the opacity is as specified in .two (because that is later in CSS file).
So what else is the alternate?
When both forward and reverse effects are required and the animation doesn't have any intermediate states (that is, there is only a start state and an end state) then it is better to use transitions instead of animations. The reason is because transitions automatically produce the reverse effect on removal of the class (unlike animations where the reverse animation needs to be written as a separate keyframe and added to the element).
Below is a sample snippet showing how you can achieve a similar effect using just one class without the need for writing keyframes.
var theBut = document.getElementById('butt');
var theBut2 = document.getElementById('butt2');
theBut.addEventListener('click', function a() {
document.querySelector('.element').classList.add('one');
});
theBut2.addEventListener('click', function b() {
document.querySelector('.element').classList.remove('one');
});
.element {
background-color: #d91e57;
display: block;
width: 160px;
height: 160px;
border-radius: 90%;
transform: scale(0.25);
opacity: 0;
transition: opacity 2s, transform .1s 2s;
}
.one {
transform: scale(1);
opacity: 0.5;
transition: all 2s;
}
<div class="element">
</div>
<button id="butt">add animation</button>
<button id='butt2'>remove animation</button>
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;
}