I’m having an issue with pure CSS-based rotation using transform: rotate()
coupled with transition: transform
.
When rotating past 360 degrees, the transition causes a counter-clockwise rotation. See code below or on CodePen.
I know I can just keep increasing/decreasing the degree value (e.g. 356->360, instead for 359->0), but this is for a React app and I’d like to simply use defined “states” with related CSS classes (i.e. class “over-1” equals the three o’clock position, “over-2” equals six o’clock, etc.).
I thought about storing the rotation value in the React state and increasing/decreasing it based on the triggering events, but I would need to inject that style directly into the HTML. Additionally, the elements I’m transforming have more than just angle being applied. I would need to also code those values in the JavaScript (e.g. transform: "translate(-50%, -50%) rotate(" + this.state.rotation + "deg);"
) or else setting the transform property would overwrite the transform from the stylesheet (I think).
Also, I hate hardcoding style information in JavaScript.
I tried using a “transitional” process (CodePen) that (1) used a class to animate the rotation from 270 to 360 degrees, (2) wait until the animation finished, then (3) switch the rotation from 360 degrees to 0 degrees (without animation), then (4) set the “correct” class to 0 degrees with the transition property (so it wouldn’t be a one-off class assignment). The problem I had with this is that clicking the rotate button before the animation completed would interrupt the transition and show counter-clockwise rotation.
I found two other questions that were loosely related, but did not provide any answers.
- How to make css-animation of rotation around full circle
- CSS rotation animation issue: creating a smooth transition from 270 degrees to -90 degrees
<!DOCTYPE html> <html> <head> <style> /* Button to trigger rotation */ #control { position: absolute; top: 210px; left: 100px; transform: translate(-50%, 0) } /* Large, underlying disk */ #under { position: absolute; top: 0; left: 0; background-color: blue; height: 200px; width: 200px; border-radius: 50%; } /* Smaller, rotating disk */ #over { position: absolute; top: 100px; left: 100px; background-color: green; height: 100px; width: 100px; z-index: 1; border-radius: 50%; } /* Simple pointer to visually track rotation */ #pointer { font-size: 50px; position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%); } /* Rotate inner disk to point to the twelve o'clock position */ .over-1 { transform: translate(-50%, -50%) rotate(0deg); transition: transform 0.5s; } /* Rotate inner disk to point to the three o'clock position */ .over-2 { transform: translate(-50%, -50%) rotate(90deg); transition: transform 0.5s; } /* Rotate inner disk to point to the six o'clock position */ .over-3 { transform: translate(-50%, -50%) rotate(180deg); transition: transform 0.5s; } /* Rotate inner disk to point to the nine o'clock position */ .over-4 { transform: translate(-50%, -50%) rotate(270deg); transition: transform 0.5s; } </style> <script> this.position = 1; /* Rotate the disk clockwise to the next position */ function rotate() { this.position += 1; if (this.position === 2) { document.getElementById("over").classList.remove("over-1"); document.getElementById("over").classList.add("over-2"); } if (this.position === 3) { document.getElementById("over").classList.remove("over-2"); document.getElementById("over").classList.add("over-3"); } if (this.position === 4) { document.getElementById("over").classList.remove("over-3"); document.getElementById("over").classList.add("over-4"); } /* PROBLEM: The transition from nine o'clock to twleve o'clock causes the disk to rotate 270 degrees COUNTER CLOCKWISE */ if (this.position === 5) { document.getElementById("over").classList.remove("over-4"); document.getElementById("over").classList.add("over-1"); this.position = 1; } } </script> </head> <body> <div id="under"></div> <div id="over" class="over-1"> <div id="pointer">^</div> </div> <div> <button id="control" onclick="rotate()">Rotate</button> </div> </body> </html>
Advertisement
Answer
As said in comments, to keep the rotation clockwise, you have to keep increasing the degrees. Thus, this is much easier to do directly in JavaScript rather than with classes:
<!DOCTYPE html> <html> <head> <style> /* Button to trigger rotation */ #control { position: absolute; top: 210px; left: 100px; transform: translate(-50%, 0) } /* Large, underlying disk */ #under { position: absolute; top: 0; left: 0; background-color: blue; height: 200px; width: 200px; border-radius: 50%; } /* Smaller, rotating disk */ #over { position: absolute; top: 100px; left: 100px; background-color: green; height: 100px; width: 100px; z-index: 1; border-radius: 50%; transform: translate(-50%, -50%) rotate(0deg); transition: transform 0.5s; } /* Simple pointer to visually track rotation */ #pointer { font-size: 50px; position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%); } </style> </head> <body> <div id="under"></div> <div id="over"> <div id="pointer">^</div> </div> <div> <button id="control">Rotate</button> </div> <script> window.rotation = 0; /* Rotate the disk clockwise to the next position */ document.getElementById("control").addEventListener('click', evt => { window.rotation += 90; document.getElementById("over").style.transform = `translate(-50%, -50%) rotate(${this.rotation}deg)`; }); </script> </body> </html>
If you want to do it with classes, you’d have to have a fifth class with 360deg
and separate transition
into a different class. When you reach 360deg
, turn off the transition
, switch to 0deg
, and turn transition back on, so it seamlessly resets without it being seen by the user.