如何在不考虑变换的情况下计算getBoundingClientRect()?

fre*_*nte 10 javascript

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

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

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

但这会导致布局抖动缓慢,特别是如果在许多元素上完成则会引起注意.现场演示:http://jsbin.com/nibiqogosa/1/edit?js,console,output

有没有更好的办法?


该javascript代码可以应用于:

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

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

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

编辑:答案综述

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

Ghe*_*lay 12

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

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

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

    el = el.offsetParent;
} while( el );
Run Code Online (Sandbox Code Playgroud)

获取元素位置而不考虑应用于它的转换,但保持DOM树的任何转换.

为此,您可以尝试还原转换.
你必须首先设置transform-origin0,0,0和自己周围的变换(缩放,旋转)宽translate(50%,50%) ... translate(-50%, -50%).这是一个例子,
改变:

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

transform: translate(50%, 50%) scale(2) rotate(45deg) translate(-50%,-50%) translate(20px);
transform-origin: 0 0 0;
Run Code Online (Sandbox Code Playgroud)

我们需要这样做,因为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
    );    
Run Code Online (Sandbox Code Playgroud)

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


Ada*_*ett 5

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

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

即使使用 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));
Run Code Online (Sandbox Code Playgroud)
.transformed {
  transform: translate(8px,8px) scale(0.5);
  transform-origin: 16px 16px;
}
Run Code Online (Sandbox Code Playgroud)
<div>Hello</div>
Run Code Online (Sandbox Code Playgroud)