Difference in CSS rotate 359 vs rotate 360

Viewed 1264

I suppose this is more academic than anything but I have an element I'm setting up to infinite spin and in my CSS file I have to add this (yes, I have my moz and webkit declarations in there too but that's not relevant here)

@keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(359deg); }
}

It seems odd not to set 360 here but it seems to work with 359. Is 360 simply equivalent to 0 or what? Trying to understand how this works and if there's a practical difference.

3 Answers

tldr:

/********* CORRECT *********/

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/********* NOT CORRECT *********/

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(359deg); }
}

full explanation:

The following syntaxes are equivalent and all produce a perfectly smooth and continuous infinite circular animation:

@keyframes spin-turn {
  from { transform: rotate(0turn); }
  to { transform: rotate(1turn); }
}
@keyframes spin-grad {
  from { transform: rotate(0grad); }
  to { transform: rotate(400grad); }
}
@keyframes spin-deg {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@keyframes spin-rad {
  from { transform: rotate(0rad); }
  to { transform: rotate(6.28318530718rad); } /* 2 × π */
}

Most importantly, there is no animation step in between 100% and 0%.

When transitioning between animation iterations, the frame corresponding to 100% is not rendered. The browser uses its value to calculate all the intermediary frames up to it (their number depends on device refresh rate and browser rendering engine, not on the CSS unit used). When the next animation iteration exists, the frame corresponding to 100% is replaced by the first frame of the next iteration (the 0% one).
The 100% frame is only rendered when animation-iteration-count is a positive finite number, after all iteration counts have been played. In other words, when the animation has stopped.


Thus, using to: { transform: rotate(359deg); } produces a jump forward in the animation of exactly 1deg. It's a jump equal to 0.2(7)% of a full turn and is therefore hardly noticeable when the animated element is small or when the speed of animation is relatively fast.

Here's a tool to compare the different syntaxes. The jump forward can be observed at the top position (when the radar line reaches the north point of the circle), with deg-359 animation syntax selected, with an animation duration above 10 seconds.

new Vue({
  el: '#app',
  data: () => ({
    units: ['turn', 'grad', 'rad', 'deg', 'deg-359'],
    animation: 'spin-deg-359',
    duration: 18
  }),
  computed: {
    rotatorStyle() {
      return {
        animation: `${this.animation} ${this.duration}s linear infinite`
      }
    }
  }
})
body {
  margin: 0;
}
* { box-sizing: border-box; }
.controls > div {
  display: flex;
  padding: 3px 0;
}
.controls {
  padding: 1rem;
}
.controls label {
  width: 80px;
}
input[type="number"] {
  width: 73px;
}
#app {
  height: 100vh;
  padding: 1rem;
  display: flex;
  align-items: flex-start;
  justify-content: space-evenly;
  overflow: hidden;
}
.ratio {
  display: grid;
  width: calc(100vh - 2rem);
}
@media(max-width: 767.98px) {
  #app {
    flex-direction: column;
    height: auto;
  }
  .ratio {
    width: 100%;
    flex: 0 0 auto;
  }
}
.ratio > * {
  grid-area: 1/1;
}
.rotator {
  display: flex;
  border: 1px solid red;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  position: relative;
}
.rotator:before {
  content: '';
  position: absolute;
  left: 50%;
  width: 50%;
  height: 0;
  top: 50%;
  transform: rotate(-90deg);
  transform-origin: 0 0;
  border-bottom: 1px solid red;
}

@keyframes spin-deg {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@keyframes spin-deg-359 {
  from { transform: rotate(0deg); }
  to { transform: rotate(359deg); }
}
@keyframes spin-rad {
  from { transform: rotate(0rad); }
  to { transform: rotate(6.28318530718rad); }
}
@keyframes spin-turn {
  from { transform: rotate(0turn); }
  to { transform: rotate(1turn); }
}
@keyframes spin-grad {
  from { transform: rotate(0grad); }
  to { transform: rotate(400grad); }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <div class="controls">
    <div>
      <label for="duration">duration</label>
      <input id="duration" v-model="duration" type="number">s
    </div>
    <div>
      <label for="animation">animation</label>
      <select id="animation" v-model="animation">
        <option v-for="unit in units" :key="unit" :value="'spin-' + unit">{{unit}}</option>
      </select>
    </div>    
  </div>
  <div class="ratio">
    <svg viewBox="0 0 1 1"></svg>
    <div class="rotator"
       :style="rotatorStyle"></div>
   </div>
  
</div>

The jump becomes more noticeable as you increase the screen size (full-page mode) or as you increase the animation-duration (i.e: 30s).

Related