【问题标题】:How to compute getBoundingClientRect() without considering transforms?如何在不考虑转换的情况下计算 getBoundingClientRect()?
【发布时间】:2015-02-28 23:29:02
【问题描述】:

getBoundingClientRect() 返回屏幕上元素被变换后的坐标。我如何计算这些坐标之前被转换?即没有转换。

我找到的最简单的方法是:

element.style.transform = 'none'; //temporarily reset the transform
var untransformedOffset = element.getBoundingClientRect().top; //get the value
element.style.transform = ''; //set it back

但这会导致缓慢的布局抖动,尤其是在许多元素上进行时尤其明显。现场演示:http://jsbin.com/nibiqogosa/1/edit?js,console,output

有没有更好的办法?


该javascript代码可应用于:

<div id="element"></div>
<style> #element { transform: translateY(20px); }</style>

结果为0(不包括页边距)

element.getBoundingClientRect().top 的结果为 20(不包括页边距)

编辑:答案汇总

http://jsbin.com/kimaxojufe/1/edit?css,js,console,output

【问题讨论】:

  • 读取变换矩阵,并对矩阵值和当前边界矩形进行一些计算。
  • 有趣,如果你把它作为答案,我会赞成。如果结果在每个浏览器中始终是 matrixmatrix3d,这将是hacky
  • @fregante 感谢您抽出宝贵的时间来完成您的“答案综述”,它是一颗宝石。和本杰明的选择一样

标签: javascript


【解决方案1】:

获取元素位置,不考虑元素和 DOM 树上的任何转换:

var el = element,
offsetLeft = 0,
offsetTop  = 0;

do{
    offsetLeft += el.offsetLeft;
    offsetTop  += el.offsetTop;

    el = el.offsetParent;
} while( el );

获取元素位置而不考虑对其应用的转换,但将任何转换保持在 DOM 树上。

为此,您可以尝试还原转换。
您必须首先将transform-origin 设置为0,0,0 并围绕自己的变换(缩放、旋转)宽度translate(50%,50%) ... translate(-50%, -50%)。 这是一个例子,
改变那个:

transform: scale(2) rotate(45deg) translate(20px);
transform-origin: 50% 50%; //default value

进入

transform: translate(50%, 50%) scale(2) rotate(45deg) translate(-50%,-50%) translate(20px);
transform-origin: 0 0 0;

我们需要这样做,因为 getComputedStyle() 返回的矩阵不包括使用 transform-origin 完成的东西。不知道为什么。

然后你可以使用这个代码:

function parseTransform(transform){
    //add sanity check
    return transform.split(/\(|,|\)/).slice(1,-1).map( function(v){
        return parseFloat(v);
    });
}

function convertCoord(transformArr, x, y, z){
    //add sanity checks and default values      

    if( transformArr.length == 6 ){
        //2D matrix
        //need some math to apply inverse of matrix
        var t = transformArr,
            det = t[0]*t[3] - t[1]*t[2];
        return {
            x: (  x*t[3] - y*t[2] + t[2]*t[5] - t[4]*t[3] )/det,
            y: ( -x*t[1] + y*t[0] + t[4]*t[1] - t[0]*t[5] )/det
        }
    }
    else /*if (transformArr.length > 6)*/{
       //3D matrix
       //haven't done the calculation to apply inverse of 4x4 matrix
    }
}

var elRect = element.getBoundingClientRect(),
    st = window.getComputedStyle(element),

    topLeft_pos = convertCoord(
              parseTransform( st.transform ),
              elRect.left,
              elRect.top,
              st.perspective
    );    

我不会解释数学部分,因为我认为这超出了本文的范围。仍然可以在其他地方解释它(可能是另一个问题?)。

【讨论】:

  • 恢复转换有点过分,但您的第一个解决方案完美而高效!
  • 我没有正确理解你的问题,我以为你只是想恢复元素转换而不是父级的转换(如果有的话)。我已经更新了我的答案,以进一步解释这两种解决方案之间的区别。
  • 是的,也许这是一个很大的模糊。标题提到了“没有(任何)转换的 gBCR”,而正文显示了一个仅还原一个转换的示例。 • 出于这个原因,我创建了"roundup" 以包含所有可能的情况。 • 您的第一个解决方案解决了我的title question(在所有情况下);本杰明解决了仅一级变换(在所有情况下)
  • 见我下面的答案 - 反转矩阵的 JS 方法很聪明,但操作顺序错误,因此尝试比它需要的更努力,并在使用比例时生成不正确的边缘位置。跨度>
【解决方案2】:

我喜欢 Ghetolay 的回答。我使用了它,但通过避免循环使其性能更高。

我有一个可拖动的标签云,我必须使用变换来更新拖动位置,但要跟踪原始位置(没有变换)。

上一个答案建议通过 offsetParents 循环。就我而言,我认为在很多情况下,标签会被转换,但容器不会。所以我只需要得到第一个 offsetParent 并在那里使用getBoundingClientRect()。无需继续循环。我解决了这个问题:

var el = element;
var parentRect = element.offsetParent.getBoundingClientRect();
var offsetLeft = parentRect.left + element.offsetLeft;
var offsetTop = parentRect.top + element.offsetTop;

【讨论】:

  • 看起来不对。输出应为 8 但为 36 jsbin.com/nufusupeji/edit?js,console,output
  • 你使用的是transformed.getBounding...,你必须使用transformed.offsetParent.getBounfing...。而且,在这种情况下,父偏移量是主体,因为两者之间没有相对定位的包装器,因此此方法还将计算主体边距。因此,offsetParent.top 为 8,element.offsetTop 也为 8。如果中间没有包装器,则可以使用 element.offsetTop。
  • 我做了一个合适的测试套件来测试这个,因为有一些变量和可能的期望行为(即定位的父母,嵌套的转换),而你的在很多情况下似乎仍然是关闭的。 jsbin.com/kimaxojufe/1/edit?css,js,console,output你能看看并做出必要的改变吗?
  • 它在任何情况下都不起作用。你看链接了吗?数字从不与“base”匹配
  • 和以前一样。有一个上边距将主体向下推 10 像素。这种方法很重要,因为两者之间没有包装器。例如,如果section 标签是position:relative,它就可以计算出正确的值。同样,这种方法需要一个相对定位的容器。
【解决方案3】:

上面在数学上反转变换的答案是一个不错的尝试,但并不完全正确(并且比它需要的更复杂)。下面是更正确的反转。

这不考虑倾斜或旋转平移,但至少在使用缩放时它会产生正确的边缘位置,并且在没有变换时不会对性能造成太大影响。

即使使用 scale(0),它也会产生准确的结果(尽管在宽度/高度上会损失亚像素精度)。

请注意,打开软件键盘的 iOS 在 getBoundingClientRect()offsetTop/offsetLeft 之间会产生不同的结果 - 后者在任何浏览器上都不支持亚像素精度。这会产生与getBoundingClientRect() 一致的结果。

function adjustedBoundingRect(el) {
  var rect = el.getBoundingClientRect();
  var style = getComputedStyle(el);
  var tx = style.transform;

  if (tx) {
    var sx, sy, dx, dy;
    if (tx.startsWith('matrix3d(')) {
      var ta = tx.slice(9,-1).split(/, /);
      sx = +ta[0];
      sy = +ta[5];
      dx = +ta[12];
      dy = +ta[13];
    } else if (tx.startsWith('matrix(')) {
      var ta = tx.slice(7,-1).split(/, /);
      sx = +ta[0];
      sy = +ta[3];
      dx = +ta[4];
      dy = +ta[5];
    } else {
      return rect;
    }

    var to = style.transformOrigin;
    var x = rect.x - dx - (1 - sx) * parseFloat(to);
    var y = rect.y - dy - (1 - sy) * parseFloat(to.slice(to.indexOf(' ') + 1));
    var w = sx ? rect.width / sx : el.offsetWidth;
    var h = sy ? rect.height / sy : el.offsetHeight;
    return {
      x: x, y: y, width: w, height: h, top: y, right: x + w, bottom: y + h, left: x
    };
  } else {
    return rect;
  }
}

var div = document.querySelector('div');
console.log(div.getBoundingClientRect(), adjustedBoundingRect(div));
div.classList.add('transformed');
console.log(div.getBoundingClientRect(), adjustedBoundingRect(div));
.transformed {
  transform: translate(8px,8px) scale(0.5);
  transform-origin: 16px 16px;
}
&lt;div&gt;Hello&lt;/div&gt;

【讨论】:

    【解决方案4】:

    改进 Ghetolay 的答案:

    获取元素位置,不考虑元素和 DOM 树上的任何转换:

    function getOffset(element)
    {
      var offsetLeft = 0;
      var offsetTop  = 0;
      
      while (element)
      {
          offsetLeft += element.offsetLeft;
          offsetTop  += element.offsetTop;
      
          element = element.offsetParent;
      }
      
      return [offsetLeft, offsetTop];
    }
    

    改进:

    • 包含在函数中
    • 循环将正确处理空元素 (element = null/undefined)

    【讨论】:

      猜你喜欢
      • 2016-05-25
      • 1970-01-01
      • 2018-01-14
      • 1970-01-01
      • 1970-01-01
      • 2022-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多