CSS 旋转动画问题:创建从 270 度到 -90 度的平滑过渡

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

我正在尝试创建一个网络动画,其中一个矩形根据鼠标位置 window 围绕浏览器的中心点旋转(如下面的 gif 所示)。

动画 99% 正常,但我 运行 遇到了无法修复的极端情况问题。查看 gif 以获取视觉参考:当鼠标位于屏幕的右半部分时,动画的行为符合预期——当鼠标位于屏幕的左半部分并穿过水平轴时,会出现问题,导致矩形翻转一个完整的 360 度而不是平滑过渡。

这是因为矩形从 270 度快速捕捉到 90 度——这两个角度在视觉上是相同的,但由于过渡动画,您可以观察到矩形翻转了整整 360 度。

如何解决此问题以确保在屏幕左侧平滑过渡?

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>

跳跃是由于 deg 从 360 变为 0。您可以将鼠标的 运行 旋转角度存储在 mousemove 处理程序之外,并计算角度的变化处理程序,像这样:

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>

值得注意的是,您不必将 deg 值限制在 0 - 360 度范围内,CSS 将根据任何给定值计算出正确的角度。保持旋转角度中的“轮数”使得旋转进入下一轮时旋转也平滑。

我能够使用变换矩阵解决问题,如下面的代码片段所示。据我所知,使用基于旋转的变换没有简单的解决方案。

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>