【问题标题】:Dragging & Resizing CSS Transformed Elements拖动和调整 CSS 变换元素的大小
【发布时间】:2012-08-14 00:44:20
【问题描述】:

例如,如果我们在矩形<div> 上设置-vendor-transform: rotate(40deg) css 属性,所有突然的拖动和调整大小都会变得非常奇怪和有缺陷。

这是一个简单的 jQueryUI 示例:http://jsfiddle.net/Ja4dY/1/

您会注意到,如果您在变换时拖动或调整该矩形的大小,它会向上或向下跳跃,并且光标不会停留在正确的位置。在我的真实代码中,我使用自定义代码来调整大小和拖动,但是我遇到了同样的问题。

嗯,“问题”当然是元素的方向会改变。所以左边可以是右边,上到下,中间有一些东西,Javascript代码仍然处理每个方向,因为它会被转换。

那么,问题来了:我们如何补偿 transformed / rotated 元素?

也欢迎任何好的资源/书籍/博客。

【问题讨论】:

  • 看起来你并不孤单。这是一个 jquery 错误:bugs.jqueryui.com/ticket/6844
  • @JohnKoerner:确实。但我对 jQuery(UI) 解决方案并不特别感兴趣。到目前为止,MichaelMullany 提供的链接非常有用。
  • (offtopic) 好消息:使用 jQuery 1.8.0+,您不再需要供应商的前缀 jsfiddle.net/Ja4dY/112

标签: javascript jquery css transform


【解决方案1】:

这不是 jQuery 中的错误。只是不支持。如果您检查 jQuery UI 源代码,您会发现它不使用转换矩阵来计算转换后的对象和页面之间的差异。

您的示例以及可能每个 jQ UI 拖动实现都因 JQ UI 源代码中的 2 个方法(大约 314 行 jquery.ui.draggable.js 文件 v1.8.23 )而受到此问题的影响。计算的偏移量与偏移量的变化无关,因为旋转是在元素中心完成的。

你必须计算出什么是变化。这是解决方法,快速而肮脏。这个想法是检查转换元素的边界框有什么不同。

在此处查看示例http://jsfiddle.net/mjaric/9Nqrh/

忽略前两次旋转的部分,它们只是为了尽量减少代码行。第三涉及计算差异的坐标系转换。执行平移后,它将向左和向上偏移(注意它在过滤器中首先出现)。

如果您想避免前两个旋转过滤器,您可以使用 2D 旋转公式编写代码:

x' = x cos f - y sin f

y' = y cos f + x sin f

其中 f 是旋转角度,但它不是那么简单,还包含更多代码行,您必须计算原始边界框的对角线角度,因为您需要 x 和 y 坐标的左上角初始角度与 x 轴(正部分)相比。然后计算 x-x' 和 y-y' 的变化。但我预测一些与变化迹象有关的问题,编码/调试将需要比我现在更多的时间。很抱歉,但我相信您在阅读这篇文章后可以弄清楚该怎么做。

【讨论】:

    【解决方案2】:

    确实,这似乎是 jQuery 中的一个错误。一个简单的解决方法是:用容器div 包围可调整大小的div。将.draggable() 设置为外部div,将.resizable() 设置为内部div。这似乎在 Ubuntu 上运行的 Chromium 中运行良好。 See Fiddle.

    我已经为外部 div 着色,让您了解引擎盖下发生的事情。

    【讨论】:

      【解决方案3】:

      您可以使用 getComputedStyle() 获取应用于元素的当前变换矩阵。您可以使用它将当前鼠标位置转换为其在转换空间中的位置,并查看单击/拖动事件是否在元素边界和/或角内。很好的资源:

      http://www.useragentman.com/blog/2011/01/07/css3-matrix-transform-for-the-mathematically-challenged/

      http://www.eleqtriq.com/2010/05/css-3d-matrix-transformations/

      顺便说一句,正如您所经历的那样,这对代码来说并非易事。我们必须为 Sencha Animator 做这件事,而且它是一头野兽。

      【讨论】:

      • 这些链接仍然非常有用。我想我可以借助这些帮助和信息来做到这一点。
      【解决方案4】:

      问题在于使元素可拖动的函数,无论是否使用 jQuery UI,都严重依赖原生的 getBoundingClientRect() 函数来确定元素的位置等。

      当应用 CSS3 变换(如旋转)时,jQuery UI 中使用的 getBoundingClientRect() 或相等的 jQuery offset() 函数的值不再按预期工作,并且鼠标指针的位置变得混乱,因为元素旋转后突然出错。

      要修复它,您需要添加某种帮助函数来重新计算值,并且有一个猴子补丁可用于此与 jQuery UI 的可拖动。

      很难说如何使相同的补丁适用于自定义代码,但您可能必须以某种方式将其集成到您的自定义函数中,这需要您进行一些编码,而且更难想出一些可以作为自定义代码开箱即用的辅助函数的东西,并且请注意,它涉及到进行这些计算,请参见下面的代码:

      function monkeyPatch_mouseStart() {
           var oldFn = $.ui.draggable.prototype._mouseStart ;
           $.ui.draggable.prototype._mouseStart = function(event) {
      
                  var o = this.options;
      
                 function getViewOffset(node) {
                    var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
                    if (node) addOffset(node);
                    return { left: x, top: y };
      
                    function getStyle(node) {
                      return node.currentStyle || // IE
                             win.getComputedStyle(node, '');
                    }
      
                    function addOffset(node) {
                      var p = node.offsetParent, style, X, Y;
                      x += parseInt(node.offsetLeft, 10) || 0;
                      y += parseInt(node.offsetTop, 10) || 0;
      
                      if (p) {
                        x -= parseInt(p.scrollLeft, 10) || 0;
                        y -= parseInt(p.scrollTop, 10) || 0;
      
                        if (p.nodeType == 1) {
                          var parentStyle = getStyle(p)
                            , localName   = p.localName
                            , parent      = node.parentNode;
                          if (parentStyle.position != 'static') {
                            x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
                            y += parseInt(parentStyle.borderTopWidth, 10) || 0;
      
                            if (localName == 'TABLE') {
                              x += parseInt(parentStyle.paddingLeft, 10) || 0;
                              y += parseInt(parentStyle.paddingTop, 10) || 0;
                            }
                            else if (localName == 'BODY') {
                              style = getStyle(node);
                              x += parseInt(style.marginLeft, 10) || 0;
                              y += parseInt(style.marginTop, 10) || 0;
                            }
                          }
                          else if (localName == 'BODY') {
                            x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
                            y += parseInt(parentStyle.borderTopWidth, 10) || 0;
                          }
      
                          while (p != parent) {
                            x -= parseInt(parent.scrollLeft, 10) || 0;
                            y -= parseInt(parent.scrollTop, 10) || 0;
                            parent = parent.parentNode;
                          }
                          addOffset(p);
                        }
                      }
                      else {
                        if (node.localName == 'BODY') {
                          style = getStyle(node);
                          x += parseInt(style.borderLeftWidth, 10) || 0;
                          y += parseInt(style.borderTopWidth, 10) || 0;
      
                          var htmlStyle = getStyle(node.parentNode);
                          x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
                          y -= parseInt(htmlStyle.paddingTop, 10) || 0;
                        }
      
                        if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
                        if ((Y = node.scrollTop))  y += parseInt(Y, 10) || 0;
                      }
                    }
                  }
      
                      this.helper = this._createHelper(event);
                      this._cacheHelperProportions();
      
                      if($.ui.ddmanager)
                          $.ui.ddmanager.current = this;
      
                      this._cacheMargins();
      
                      this.cssPosition = this.helper.css("position");
                      this.scrollParent = this.helper.scrollParent();
      
                  this.offset = this.positionAbs = getViewOffset(this.element[0]);
                      this.offset = {
                          top: this.offset.top - this.margins.top,
                          left: this.offset.left - this.margins.left
                      };
      
                      $.extend(this.offset, {
                          click: {
                              left: event.pageX - this.offset.left,
                              top: event.pageY - this.offset.top
                          },
                          parent: this._getParentOffset(),
                          relative: this._getRelativeOffset()
                      });
      
                      this.originalPosition = this.position = this._generatePosition(event);
                      this.originalPageX = event.pageX;
                      this.originalPageY = event.pageY;
      
                      (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
      
                      if(o.containment)
                          this._setContainment();
      
                      if(this._trigger("start", event) === false) {
                          this._clear();
                          return false;
                      }
      
                      this._cacheHelperProportions();
      
                      if ($.ui.ddmanager && !o.dropBehaviour)
                          $.ui.ddmanager.prepareOffsets(this, event);
      
                      this.helper.addClass("ui-draggable-dragging");
                      this._mouseDrag(event, true);
      
                      if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
                      return true;
           };
       }
      monkeyPatch_mouseStart();
      

      这里有一个FIDDLE 显示它与 jQuery UI 的可拖动和可调整大小一起按预期工作!

      【讨论】:

      • 如果您只是想解决看起来很奇怪的拖动问题,那么像您这样的解决方案是“好的”。毕竟,这仍然是一个幻觉。唯一真正的解决方案在于数学和转换矩阵。你不能用这样的方法来解决调整大小的问题。
      • 这适用于可拖动,但不适用于可调整大小。我正在使用 chrome,当您开始调整大小时,调整大小仍然会使其跳跃。我在我的项目中遇到了同样的问题,我错过了什么吗?
      【解决方案5】:

      你说你对 JQuery 解决方案不感兴趣,

      • 一个解决方案是;

        我建议您编写自己的拖动和调整大小函数。你可以 处理旋转对象的大小调整和拖动以将其顶部和左侧添加该度数的正弦和余弦。

      • 另一种解决方案是;

        您可以使用 Raphael JS 之类的库来创建要转换的对象, 拖动和调整大小。 Raphael JS 使用 svg!

        For more information about Raphael JS

      • 另一种解决方案是;

        如果你不想使用像 Raphael JS 这样的库,你可以直接使用 SVG 和 JQuery

        For more information about SVG

      现在不能写更多细节,我明天扩展这个解决方案。

      暂时希望这些帮助。

      【讨论】:

        【解决方案6】:

        我找到了这个...这是一个工作示例,外加信息、演示和下载链接。

        jquery-ui-rotation-using-css-transform -> live-demo

        他使用他自己的图书馆,但如果你对这个主题感兴趣,你可以阅读并了解他是如何获得它的。

        干杯,祝你好运。

        转基因-

        顺便说一句,网络是俄语的,但您可以使用google translate 管理 ;-)

        【讨论】:

        • 这个演示遇到了和原来一样的问题 - 它没有考虑到拖动手柄随着对象旋转的事实:尝试在演示中将地球旋转 180 度,然后向左拖动把手向左...
        • 我在 Chrome 和 Firefox(都在 win 中)进行了测试,演示工作非常完美。您可以旋转、拖动和调整大小来解决问题。
        • 在 OSX 下的 Safari/Chrome 中肯定坏了。
        • 我猜那个演示的作者已经有点接近了。我会看看它,但它仍然需要一些优化。
        【解决方案7】:

        如果我们覆盖 cursorAt 看起来会更好:

        $("#foo").mousedown(function (e) { 
            var x = e.pageX - this.offsetLeft;
            var y = e.pageY - this.offsetTop;
            console.log(x);
            $("#foo").draggable("option", "cursorAt", {left: x, top:y});
        });
        

        更新小提琴:http://jsfiddle.net/johnkoer/Ja4dY/8/

        【讨论】:

        • 存在问题,它并没有真正解决任何问题。对于拖动它看起来更好,但我们仍然不真正“知道”例如角的 x 和 y 位置。调整大小也仍然失败。
        猜你喜欢
        • 2012-06-07
        • 1970-01-01
        • 1970-01-01
        • 2012-04-30
        • 1970-01-01
        • 2016-04-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多