Direction of rotation in CSS Animation\Transformation - css

I'm trying to understand CSS animations and I'm running into the following issue:
I've got a simple div with animation attached to it in CSS, like below:
<div id="learn">LEARN</div>
#learn {
width: 100px;
height: 100px;
background: blue;
position: relative;
animation: test1 5s ease-in 2s infinite;
}
Case 1:
#keyframes test1 {
0% {
transform: rotate(179deg);
}
100% {
transform: rotate(357deg);
}
}
Case 2 (the 0% transform is split into 90+89 deg instead of 179):
#keyframes test1 {
0% {
transform: rotate(90deg) rotate(89deg);
}
100% {
transform: rotate(357deg);
}
}
Why is the first case rotating clockwise and the second case rotating counter clockwise?

This might just be a bug with rotation transforms, which I'll address after some background.
Background
First off, the way rotations are supposed to work is with relation to a circle. If you specify a number of degrees, that will place the element in the position dictated by a how much rotation the degrees specify. When not animating a rotation, there are many ways to represent the element's placement. Code Uniquely is half-right in their comment in that the placement of 357 degrees and -3 degrees is the same, but when animating, they're very different. Going from 0 degrees to -3 degrees is a small counter-clockwise rotation, whereas going from 0 degrees to 357 degrees is a large clockwise rotation.
Findings
What you've found seems to ignore this calculation (in both Firefox and Chrome from what I've tested out). From what I'm seeing, combining rotate transforms effectively reverses the direction that the rotation should be going in, even if you're combining with a 0deg rotation:
transform: rotate(90deg); //rotates clockwise
transform: rotate(0deg) rotate(90deg); //rotates counter-clockwise
It seems that you can fix this by "combining" the rotation transforms in both animation steps:
// this performs a clockwise rotation
#keyframes test3 {
0% {
transform: rotate(0deg) rotate(90deg);
}
100% {
transform: rotate(0deg) rotate(357deg);
}
}
Finally, it seems that the number of "combinations" matters. If you combine 2 rotations in one step, but 3 in another, the unexpected behavior occurs:
// rotates counter-clockwise since there are unequal rotation transforms
#keyframes test4 {
0% {
transform: rotate(0deg) rotate(0deg) rotate(90deg);
}
100% {
transform: rotate(0deg) rotate(357deg);
}
}
Conclusion
This likely isn't the greatest answer, but combining rotations like this just doesn't appear to be documented anywhere. I would suggest not combining the same rotate transforms (you can still combine rotateX and rotateY without this weirdness) and stick with the sum of the degree values.
In case anyone finding this explanation wants to perhaps find out more about this behavior than I have, here's a fiddle with the examples above as a starting point.

Related

Rotate image from random start location in React using Keyframes

Using React, I have a component that I want to start rotated from a random location and rotate 360 degrees infinitely. What I have is:
HTML:
return <img className={`${props.className}`} style={{
animation: `spin 5s linear infinite`,
transform: `rotate(${Math.floor(Math.random() * (361));}deg)`
}} src={image} alt="img"/>
CSS:
#keyframes spin {
from {transform:rotate(0deg);}
to {transform:rotate(360deg);}
}
By default, what this does is ignore the transform, load the image so that it is upright (i.e. 0 degrees), and rotate to 360 degrees (vertical again) over the course of 5 seconds. What I want is a way for from to be transform:rotate(deg);} and to to be {transform:rotate(<SOME RANDOM NUMBER + 360>deg);}. Is this possible?
As a side note, is it possible to randomize the direction it travels?
You can't get the required effect using transform rotate - as you have noted the animation will immediately alter this to rotate(0).
But what you can do is leverage the animation-delay CSS property.
Instead of calculating a random rotation, calculate a random time between 0 and 5 seconds and use that, negative, to set animation-delay.The animation will start part way through its cycle, at a random point and so at a random angle.
Here's a pure CSS/HTML/JS snippet with the rotation slowed down so it's easier to see that the rotation does start at a different angle each time:
const spinner = document.querySelector('.spin');
spinner.style.setProperty('--delay', Math.floor(Math.random() * 50));
.spin {
width: 100px;
height: 100px;
background: red;
animation: spin 50s linear infinite;
animation-delay: calc(var(--delay) * -1s);
}
#keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
<div class="spin"></div>

Delay after each CSS translateX animation

I'm using CSS transform translateX to move a Gif from left to right. The gif stops and belches towards the end of the Gif animation but the Gif is still being translated along the X co-ordinate, so it doesn't look good!
How can I add a pause/delay at the end of the translateX.
There's a similar question on Stackoverflow regarding a Rotate, and the answer is the following code:
66%, 100% {
transform:rotatey(0deg);
}
But for the life of me I can't figure out how to apply to mine.
.gif-image {
width: 30%;
animation-name: slideLeftToRight;
animation-duration: 20s;
animation-iteration-count: infinite;
}
#keyframes slideLeftToRight {
from {transform: translateX(0px)}
to {transform: translateX(400px)}
}
body{
background-color: #6dba94;
}
<div class="container">
<img class="gif-image" src="https://cdn.dribbble.com/users/672882/screenshots/1972683/character-rig-800x600.gif" />
</div>
This is an interesting one since you can't use a transition delay quite the same as in most circumstances since the GIF never stops. So your case basically boils down to timing up the animation to match with the GIF timing. This is going to be hard to do, especially if you didn't make the GIF yourself. (If you did, maybe it'd be easier for you to tell exactly when the guy starts and stops walking, in milliseconds.)
That said, hard does not mean impossible.
Important note before we start: The GIF is gonna keep playing over and over, even if you update your code in JS Fiddle or CodePen or a Stack Snippet or what have you. So you can end up thinking your animation timing is all off when it's actually not. In testing this, I copied the URL I was working from in CodePen and then kept closing the tab, reopening a new one, and pasting the URL into there to reload it with the GIF starting from its beginning. This is tedious, but I don't know a better way.
Anyway...
First you gotta figure out how long the GIF lasts because it'll make our life a million times easier to base the animation duration around the GIF's duration. I was guessing initially that it'd be a nice round number like exactly 5 seconds, but in testing it seemed to get off after a few iterations, like it was actually slightly longer. I'm ballparking it at 5.15 seconds.
So let's make our guy walk to the right when he's done belching. First we gotta figure out how long exactly he takes before he starts walking. (If you know this number exactly, your life will be way easier. I had to use guess and check.)
With some testing, I figured out he starts walking approximately 42% of the way into the GIF, or about 2163ms into it, if the GIF is 5.15 seconds long (5150ms). So we don't start our translation until that point:
#keyframes animationName {
0% {
transform: translateX(0px);
}
42% {
transform: translateX(0px);
}
100% {
transform: translateX(200px);
}
}
That basically gives us this:
.gif-image {
width: 250px;
animation-iteration-count: infinite;
animation-name: slideLeftToRightRepeating;
animation-duration: 5.15s;
}
#keyframes slideLeftToRightRepeating {
0% {
transform: translateX(0px);
}
42% {
transform: translateX(0px);
}
100% {
transform: translateX(200px);
}
}
body {
background-color: #6dba94;
}
<div class="container">
<div><img class="gif-image" src="https://cdn.dribbble.com/users/672882/screenshots/1972683/character-rig-800x600.gif" /></div>
</div>
Once we get that part down, if you want him to keep walking sideways like you have in your question with a long animation duration, it's just a matter of multiplying our animation duration by however many times you want to repeat the motion, and then dividing our percentages by that same amount.
So let's say you want to have him repeat the animation five times. Our animation duration now becomes (5 * 5.15s) = 20.75s.
Then, in our percentages, every 20% of the animation (because we're dividing it in fifths) will be a repeat of the pattern we established above. So at the 20% mark, we have the same effect as at the 100% mark above.
We figured out he starts walking around 42% of the way through, so we take 42% of 20% to get 8.4%. Our keyframes now look like this:
#keyframes animationName {
0% {
transform: translateX(0px);
}
8.4% {
transform: translateX(0px);
}
20% {
transform: translateX(200px);
}
28.4% {
transform: translateX(200px);
}
40% {
transform: translateX(400px);
}
.....
}
In practice, this proved to be not quite right (I guess my 42% estimation isn't perfect). Revising our numbers a bit, I came up with 0%, 11.5%, 20%, 31.5%, etc.
At the end, we have 100% { transform: translateX(1000px); }. That's a 200px translation five times.
Here's a pretty good starting spot where things go decently well, but the timing isn't exact. I'll leave it up to you to mess with it to get the numbers exactly right and also to adjust the distance he walks each time to fit your circumstance. Hopefully this helps.
Note that I think your life will be easier if you set the width of the GIF as well as the translation distance in fixed units, otherwise you may find that with relative units like percentages that your guy appears to either be walking in place or taking huge bounds across the screen depending how wide it is.
.gif-image {
width: 250px;
animation-iteration-count: infinite;
animation-duration: 20.75s;
animation-name: slideLeftToRightSlowly;
}
#keyframes slideLeftToRightRepeating {
0% {
transform: translateX(0x);
}
42% {
transform: translateX(0px);
}
100% {
transform: translateX(200px);
}
}
#keyframes slideLeftToRightSlowly {
0% {
transform: translateX(0px);
}
11.5% {
transform: translateX(0px);
}
20% {
transform: translateX(200px);
}
31.5% {
transform: translateX(200px);
}
40% {
transform: translateX(400px);
}
51.5% {
transform: translateX(400px);
}
60% {
transform: translateX(600px);
}
71.5% {
transform: translateX(600px);
}
80% {
transform: translateX(800px);
}
91.5% {
transform: translateX(800px);
}
100% {
transform: translateX(1000px);
}
}
body {
background-color: #6dba94;
}
<div class="container">
<div><img class="gif-image" src="https://cdn.dribbble.com/users/672882/screenshots/1972683/character-rig-800x600.gif" /></div>
</div>

Safari CSS Bug: Animation Rotation Direction Incorrect?

I'm writing a custom animation for a project I'm working on. The idea is that the animation is meant to resemble an exclamation mark becoming unhinged from the baseline, swinging for a moment, and then falling off the baseline entirely.
For some reason, only Safari (OSX and iOS) refuses to render the first animation keyframe as a clockwise rotation in a CSS animation I've made. Instead, it renders the keyframe as a counter-clockwise animation, but then works just fine for the following animation keyframes.
Working CodePen example: http://codepen.io/michaelmarcialis/pen/obPYPO
#keyframes unhinged {
0% {
transform: rotate(0deg);
}
15% {
transform: rotate(240deg);
}
30% {
transform: rotate(125deg);
}
45% {
transform: rotate(220deg);
}
60% {
transform: rotate(145deg);
}
75% {
opacity: 1;
transform: rotate(200deg);
}
90% {
opacity: 0;
transform: translate(-0.5rem, 8.57142857142857rem) rotate(215deg);
}
95% {
opacity: 0;
transform: translate(0) rotate(0deg);
}
100% {
opacity: 1;
transform: rotate(0deg);
}
}
All other browsers render the animation as intended, with the first animation keyframe rotating clockwise. Safari is the only one that applies a counter-clockwise rotation in the initial keyframe. I'm assuming Safari is doing this because the distance to travel the rotation is shorter when going counter-clockwise, but it's not honoring the CSS spec properly if that's the case.
Does anyone know a remedy for this?
The problem is that if you try to animate a rotate in safari greater than 180 degrees, it will instead rotate the other way. So if you try to rotate +270 degrees, Safari will animate a rotation of -90 degrees.
The workaround for Safari is to never rotate more than 179 in either direction, then complete the rest of the rotation in another segment.

Rotate animation and translate?

The rotate animation won't work with translate. I get, that I have to put translate in the same property with the rotate, but how is this possible when using keyframes? Code is like so:
#-webkit-keyframes rotating {
from{
-webkit-transform: rotate(0deg);
}
to{
-webkit-transform: rotate(360deg);
}
}
#keyframes rotating {
from{
transform: rotate(0deg);
}
to{
transform: rotate(360deg);
}
}
img{
-webkit-transform:translate(-50%,-50%);
transform:translate(-50%,-50%);
-webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
position:absolute;
top:50%;
left:50%;
}
This will make the rotation, but it disables the translate. If I put the translate into the rotating animation, the translate is being animated as well(ofcourse).
The 2022+ answer
You can work around this by using individual property transforms which have pretty good browser support.
In short, use the properties like scale, translate, etc. instead of transform.
Side note: You don't need browser-specific properties for anything related to transforms or animations these days.
The original 2014 answer
The issue is that the transform in the animation is overriding the default transform:translate. In this case, you can combine them in the animation itself but it has to be hard coded.
#keyframes rotating {
from {
transform: translate(-50%,-50%) rotate(0deg);
}
to {
transform: translate(-50%,-50%) rotate(360deg);
}
}
If you need it to be dynamic, you can nest it in an element and animate one while not affecting the other - most likely translate the parent and rotate the child.
If you absolutely cannot have more than one element, you can affect the transform matrix for the element using JavaScript, in which case using an animation library like GSAP would be advantageous.

How do you spin side to side using css3 animation?

I have an image of a face. I want to spin/rotate it side to side. 20 degrees to the right and 20 degrees to the left.
So far this is my code:
<img class="image">
CSS
.image {
-webkit-animation:spin 1.8s linear infinite;
}
#-webkit-keyframes spin {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(20deg); }
}
I can get it to move one way, but how do I make it go back the other way?
I was thinking it should work like this: spin from 0 deg to 20 deg from 20 deg to -20 degrees. I tried it but it doesn't work.
You would not be able to achieve it using from and to in animations but you can use the percentage option like below:
#-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
50% {
-webkit-transform: rotate(20deg);
}
100% {
-webkit-transform: rotate(-20deg);
}
}
Explanation:
For the first 50% of the animation duration (0-0.9s), it would rotate the image from 0 to 20 degrees.
For the next 50% (50% - 100%), it would rotate the image from 20 degrees to -20 degrees.
For the animation to have a more smoother look, you could also a rotate to 0 degree in between and at the end (as shown in Sample 2 below).
If you want 0 to 20 alone to happen in a 1.8s window and 20 to -20 degrees to take another 1.8s then you can increase the animation duration to 3.6s in the -webkit-animation property value like below (at the end).
Note: I assume you are aware that with the -webkit- prefix, the animation effect would be seen only in webkit powered browsers. For Firefox, you would have to add -moz- for the animations, keyframes and transform properties and for future compliance you would have to add the standard un-prefixed versions also.
.image {
-webkit-animation:spin 3.6s linear infinite;
}
Fiddle Demo
Sample 2 - with a smoother rotation effect
#-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
}
25% { /* note that rotation from 0 to 20 deg happens in 25% time = 0.45 seconds. */
-webkit-transform: rotate(20deg);
}
50% {
-webkit-transform: rotate(0deg);
}
75% {
-webkit-transform: rotate(-20deg);
}
100% {
-webkit-transform: rotate(0deg);
}
}
Demo for Sample 2

Resources