旋转时使用拖动手柄调整 div 的大小

5
我发现类似的问题涉及到jQuery UI库,或者只有没有拖动句柄的css,但是没有使用纯数学的情况。我尝试实现的是可调整大小和可旋转的div。到目前为止,很容易做到。但是当旋转时,调整大小的句柄会以相反的方式进行计算:当从形状中拖动时,它会减小而不是增加大小。除了计算之外,我希望能够根据旋转改变调整大小的句柄光标,始终让它有意义。为此,我想检测调整大小的句柄位于哪个象限,并应用一个类来通过css更改光标。我的要求是仅使用jQuery而无需其他内容,不要重新发明轮子,但我希望有一种轻量级代码和简单UI的解决方案。我可以开发到达这一点,但它对我来说现在变得太复杂了...我非常困扰,所以我需要您的帮助来检测何时旋转足够使计算反转。最终,如果有人有想法或更好的示例要向我展示,我正在寻找UX改进!这是我的代码和Codepen:http://codepen.io/anon/pen/rrAWJA
<html>
<head>
    <style>
    html, body {height: 100%;}

    #square {
        width: 100px;
        height: 100px;
        margin: 20% auto;
        background: orange;
        position: relative;
    }
    .handle * {
        position: absolute;
        width: 20px;
        height: 20px;
        background: turquoise;
        border-radius: 20px;
    }
    .resize {
        bottom: -10px;
        right: -10px;
        cursor: nwse-resize;
    }
    .rotate {
        top: -10px;
        right: -10px;
        cursor: alias;
    }
    </style>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script>
        $(document).ready(function()
        {
            new resizeRotate('#square');
        });

        var resizeRotate = function(targetElement)
        {
            var self = this;
            self.target = $(targetElement);
            self.handles = $('<div class="handle"><div class="resize" data-position="bottom-right"></div><div class="rotate"></div></div>');
            self.currentRotation = 0;
            self.positions = ['bottom-right', 'bottom-left', 'top-left', 'top-right'];

            self.bindEvents = function()
            {
                self.handles
                    //=============================== Resize ==============================//
                    .on('mousedown', '.resize', function(e)
                    {
                        // Attach mouse move event only when first clicked.
                        $(document).on('mousemove', function(e)
                        {
                            var topLeft = self.target.offset(),
                                bottomRight = {x: topLeft.left + self.target.width(), y: topLeft.top + self.target.height()},
                                delta = {x: e.pageX - bottomRight.x, y: e.pageY - bottomRight.y};

                            self.target.css({width: '+=' + delta.x, height: '+=' + delta.y});
                        })
                        .one('mouseup', function(e)
                        {
                            // When releasing handle, round up width and height values :)
                            self.target.css({width: parseInt(self.target.width()), height: parseInt(self.target.height())});
                            $(document).off('mousemove');
                        });
                    })
                    //============================== Rotate ===============================//
                    .on('mousedown', '.rotate', function(e)
                    {
                        // Attach mouse move event only when first clicked.
                        $(document).on('mousemove', function(e)
                        {
                            var topLeft = self.target.offset(),
                                center = {x: topLeft.left + self.target.width() / 2, y: topLeft.top + self.target.height() / 2},
                                rad = Math.atan2(e.pageX - center.x, e.pageY - center.y),
                                deg = (rad * (180 / Math.PI) * -1) + 135;

                            self.currentRotation = deg;
                            // console.log(rad, deg);
                            self.target.css({transform: 'rotate(' + (deg)+ 'deg)'});
                        })
                        .one('mouseup', function(e)
                        {
                            $(document).off('mousemove');
                            // console.log(self.positions[parseInt(self.currentRotation/90-45)]);
                            $('.handle.resize').attr('data-position', self.positions[parseInt(self.currentRotation/90-45)]);
                        });
                    });
            };
            self.init = function()
            {
                self.bindEvents();
                self.target.append(self.handles.clone(true));
            }();
        }
    </script>
</head>
<body>
    <div id="all">
        <div id="square"></div>
    </div>
</body>
</html>

感谢您的帮助!

将鼠标位置转换为矩形的本地坐标系(使用其逆变换),然后您就得到了基本情况,您似乎已经处理得很好了。 - Nico Schertler
谢谢你的帮助。但是如何检测何时需要反转,您必须检测我们是否处于某个象限中,对吗? - antoni
为什么要检测呢?它总是反向的。如果没有被转换(即变换矩阵是单位矩阵),那么逆矩阵也将是单位矩阵。 - Nico Schertler
感谢您的支持,但我需要更多的帮助。我修改了我的问题并开始了悬赏。你能帮我编辑代码或者给我一个示例吗? - antoni
2个回答

3

尼克的答案大部分是正确的,但最终结果计算是错误的,这也导致了屏幕闪烁。实际情况是,大小的增加或减少被乘以2倍。应该将原始的半宽度加上新的半宽度来计算正确的新宽度和高度。

与其做这样的运算:

self.w = 2 * mouseLocalX;
self.h = 2 * mouseLocalY;

应该是这样的:
self.w = (self.target.width() / 2) + mouseLocalX;
self.h = (self.target.height() / 2) + mouseLocalY;

1
谢谢@thomas,我将Nico的答案粘贴到了Codepen中,并使用您的更正进行了编辑,现在它运行得很好!太糟糕了,两年前我放弃了这个想法,也放弃了jQuery哈哈。https://codepen.io/anon/pen/OwREWe - antoni

3
这是修改后的代码,实现了你想要的功能:
$(document).ready(function() {
  new resizeRotate('#square');
});

var resizeRotate = function(targetElement) {
  var self = this;
  self.target = $(targetElement);
  self.handles = $('<div class="handle"><div class="resize" data-position="bottom-right"></div><div class="rotate"></div></div>');
  self.currentRotation = 0;
  self.w = parseInt(self.target.width());
  self.h = parseInt(self.target.height());
  self.positions = ['bottom-right', 'bottom-left', 'top-left', 'top-right'];

  self.bindEvents = function() {
    self.handles
      //=============================== Resize ==============================//
      .on('mousedown', '.resize', function(e) {
        // Attach mouse move event only when first clicked.
        $(document).on('mousemove', function(e) {
            var topLeft = self.target.offset();           

            var centerX = topLeft.left + self.target.width() / 2;
            var centerY = topLeft.top + self.target.height() / 2;

            var mouseRelativeX = e.pageX - centerX;
            var mouseRelativeY = e.pageY - centerY;

            //reverse rotation
            var rad = self.currentRotation * Math.PI / 180;
            var s = Math.sin(rad);
            var c = Math.cos(rad);
            var mouseLocalX = c * mouseRelativeX + s * mouseRelativeY;
            var mouseLocalY = -s * mouseRelativeX + c * mouseRelativeY;

            self.w = 2 * mouseLocalX;
            self.h = 2 * mouseLocalY;
            self.target.css({
              width: self.w,
              height: self.h
            });
          })
          .one('mouseup', function(e) {
                $(document).off('mousemove');
          });
      })
      //============================== Rotate ===============================//
      .on('mousedown', '.rotate', function(e) {
        // Attach mouse move event only when first clicked.
        $(document).on('mousemove', function(e) {
            var topLeft = self.target.offset(),
              center = {
                x: topLeft.left + self.target.width() / 2,
                y: topLeft.top + self.target.height() / 2
              },
              rad = Math.atan2(e.pageX - center.x, center.y - e.pageY) - Math.atan(self.w / self.h),
              deg = rad * 180 / Math.PI;

            self.currentRotation = deg;
            self.target.css({
              transform: 'rotate(' + (deg) + 'deg)'
            });
          })
          .one('mouseup', function(e) {
            $(document).off('mousemove');
            $('.handle.resize').attr('data-position', self.positions[parseInt(self.currentRotation / 90 - 45)]);
          });
      });
  };
  self.init = function() {
    self.bindEvents();
    self.target.append(self.handles.clone(true));
  }();
}

主要更改如下:
在调整大小事件中,根据当前旋转角度将鼠标位置转换为本地坐标系。然后根据鼠标在本地坐标系中的位置确定大小。
旋转事件考虑方框的纵横比 (即 - Math.atan(self.w / self.h) 部分)。
如果想根据当前旋转角度更改光标,请检查手柄的角度 (即 self.currentRotation + Math.atan(self.w / self.h) * 180 / Math.PI)。例如,如果您有一个每个象限的光标,请检查此值是否介于 0..90、90..180 等范围内。如果 atan2 返回负数,您可能需要查看文档。
注意:偶尔出现的闪烁是由于框未垂直居中造成的。

谢谢您的回复。这正是我所寻找的。除了现在我看到的结果会闪烁、如果玩得太多会失去控制,并且总体上不太用户友好,我对我的想法有点失望。有没有人可以建议一些用户体验改进呢? :) - antoni
如果注意一些细节,闪烁可能可以避免。上面的代码只是为了说明转换为本地坐标。您所说的“失去句柄”是什么意思? - Nico Schertler
当您旋转并拖动时,有时会丢失手柄,就像尝试将元素拖出浏览器一样。因此,我更新了我的CodePen,并添加了min-width和min-height以防止正方形在您拖动到0时无法使用。http://codepen.io/anon/pen/BLgVOZ。闪烁仍然存在,我理解您所说的框不是垂直对齐,但如果此工具应用于所见即所得的图像上,我该如何做到这一点。我无法使它们垂直对齐。为什么闪烁发生在底部而在我的第一次尝试中没有发生? - antoni
如果你在缩放后调整位置以修正中心,那可能会有所帮助。 - Nico Schertler
你所说的“fix center”是指在拖动时进行绝对/相对定位和计算顶部/左侧位置吗? - antoni
是的,那就是我的意思。 - Nico Schertler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接