Search code examples
javascriptanimationrotationcss-animationsimage-rotation

CSS rotation animation issue: creating a smooth transition from 270 degrees to -90 degrees


I am trying to create a web animation where a rectangle rotates around the centre point of the browser window depending on the mouse position (as shown in the gif below).

The animation is 99% working, but I've run into an edge case issue that I am unable to fix. See gif for a visual reference: the animation behaves as desired when the mouse is in the right half of the screen--the issue occurs when the mouse is in the left half of the screen and crosses the horizontal axis, causing the rectangle to flip a full 360 degrees instead of transitioning smoothly.

This is because the rectangle is quickly snapping from 270 degrees to 90 degrees--both angles are visually the same, but due to transition animations you can observe the rectangle flipping a full 360 degrees.

How can I fix this issue to ensure a smooth transition on the left-side of the screen?

enter image description here

var rect = document.getElementById('orange_rect'); // Target rectangle
var window_width = $(window).width();
var window_height = $(window).height();

$(window).resize(function() { //set window width and height again everytime the window is resized
  var window_width = $(window).width();
  var window_height = $(window).height();
});

// Update rotation degrees on mousemove
$(document).mousemove(function(e) {
  var x_pos = e.pageX / window_width;
  var y_pos = e.pageY / window_height;
  if (y_pos >= 0.5) {
    var deg = 270 - (x_pos * 180);
  } else if (y_pos < 0.5) {
    var deg = (x_pos * 180) - 90;
  };
  rect.style.webkitTransform = 'rotate(' + deg + 'deg)';
  rect.style.mozTransform = 'rotate(' + deg + 'deg)';
  rect.style.msTransform = 'rotate(' + deg + 'deg)';
  rect.style.oTransform = 'rotate(' + deg + 'deg)';
  rect.style.transform = 'rotate(' + deg + 'deg)';
});
body {
  overflow: hidden;
}

#orange_rect {
  /* Homepage orange rect */
  background-color: #FF4734;
  height: 100vh;
  width: 200vw;
  overflow: hidden;
  position: absolute;
  top: 50vh;
  margin-right: auto;
  margin-left: -50vw;
  transform-origin: 50% 0;
  transition: transform 0.3s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<body>
  <div id="orange_rect"></div>
</body>


Solution

  • I was able to solve the issue using transform matrices as demonstrated in the snippet below. There is no easy solution using rotation-based transform, as far as I can tell.

    var rect = document.getElementById('orange_rect'); // Target rectangle
    var window_width = $(window).width();
    var window_height = $(window).height();
    
    $(window).resize(function() { //set window width and height again everytime the window is resized
      var window_width = $(window).width();
      var window_height = $(window).height();
    });
    
    // Update rotation degrees on mousemove
    $(document).mousemove(function(e) {
      var window_width = $(window).width();
      var window_height = $(window).height();
      var x_pos = e.pageX / window_width; // proportion mouse position from left
      var y_pos = e.pageY / window_height; // proportion mouse poisiton from top
    
      if (x_pos <= 0.5) { // If mouse is on the left half of the screen
        if (y_pos <= 0.5) { // If mouse is in top-left quadrant
          n1 = 2 * x_pos;
          n2 = -1 + 2 * x_pos;
          n3 = 1 - 2 * x_pos;
          n4 = n1;
        }
        if (y_pos > 0.5) { // If mouse is in bottom-left quadrant
          n1 = -2 * x_pos;
          n2 = -1 + 2 * x_pos;
          n3 = 1 - 2 * x_pos;
          n4 = n1;
        }
      } else if (x_pos > 0.5 && x_pos <= 1) { // If mouse is on the right half of the screen
        if (y_pos <= 0.5) { // If mouse is in top-right quadrant
          n1 = 1 - 2 * (x_pos - 0.5);
          n2 = 2 * (x_pos - 0.5);
          n3 = -2 * (x_pos - 0.5)
          n4 = n1
        }
        if (y_pos > 0.5) { // If mouse is in bottom-right quadrant
          n1 = -1 + 2 * (x_pos - 0.5);
          n2 = 2 * (x_pos - 0.5);
          n3 = -2 * (x_pos - 0.5)
          n4 = n1
        }
      };
      // console.log(n1)
      rect.style.webkitTransform = 'matrix(' + n1 + ',' + n2 + ',' + n3 + ',' + n4 + ',0,0)';
      rect.style.mozTransform = 'matrix(' + n1 + ',' + n2 + ',' + n3 + ',' + n4 + ',0,0)';
      rect.style.msTransform = 'matrix(' + n1 + ',' + n2 + ',' + n3 + ',' + n4 + ',0,0)';
      rect.style.oTransform = 'matrix(' + n1 + ',' + n2 + ',' + n3 + ',' + n4 + ',0,0)';
      rect.style.transform = 'matrix(' + n1 + ',' + n2 + ',' + n3 + ',' + n4 + ',0,0)';
    });
    body {
      overflow: hidden;
    }
    
    #orange_rect {
      /* Homepage orange rect */
      background-color: #FF4734;
      height: 200vh;
      width: 200vw;
      overflow: hidden;
      position: absolute;
      top: 50vh;
      margin-right: auto;
      margin-left: -50vw;
      transform-origin: 50% 0;
      transition: transform 0.3s;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <body>
      <div id="orange_rect"></div>
    </body>