Hassan A
Hassan A

Reputation: 337

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>

Upvotes: 1

Views: 1055

Answers (2)

Hassan A
Hassan A

Reputation: 337

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>

Upvotes: 0

Teemu
Teemu

Reputation: 23406

The jump is due to the deg changing from 360 to 0. You could store the running rotation angle of the mouse outside of the mousemove handler, and calculate the change to the angle in the handler, like this:

var rect = document.getElementById('orange_rect'), // Target rectangle
  window_width = $(window).width(), // set window width
  window_height = $(window).height(), // set window height
  angle = 0, // The stored angle of the mouse [rad]
  deg = 0, // The actual rotation angle [deg]
  cX = 200, // The centre point of the rotation
  cY = 200;

$(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) { //called whenever mouse moves in browser window
  var x = e.pageX, // proportion representing mouse position from left of screen
    y = e.pageY, // proportion representing mouse position from top of screen
    ang = Math.atan2(y - cY, x - cX), // The current angle of the mouse related to the rotation centre
    delta = ang - angle; // Change to the previous angle

  angle += delta;
  deg += delta * 180 / Math.PI;

  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)';
});
#orange_rect {
  position: fixed;
  top: 100px;
  left: 100px;
  width: 200px;
  height: 200px;
  background: orange;
  border-top: 5px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="orange_rect"></div>

It's notable, that you don't have to limit deg value to range of 0 - 360, CSS will calculate the correct angle from any given value. Keeping the "rounds" in the rotation angle makes the rotation smooth also when the rotation goes to the next round.

Upvotes: 1

Related Questions