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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27745438/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 07:55:30  来源:igfitidea点击:

How to compute getBoundingClientRect() without considering transforms?

javascript

提问by fregante

getBoundingClientRect()returns the coordinates of an element on the screen afterbeing transformed. How do I calculate those coordinates beforebeing transformed? i.e. without transforms.

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

The simplest way I found was:

我发现的最简单的方法是:

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

but that causes slow layout thrashing, especially noticeable if done on many elements. Live demo: http://jsbin.com/nibiqogosa/1/edit?js,console,output

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

Is there a better way?

有没有更好的办法?



That javascript code can be applied to:

该 javascript 代码可以应用于:

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

And the result will be 0 (excluding the page's margin)

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

The result of element.getBoundingClientRect().topwill be 20 (excluding the page's margin)

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

Edit: Answers roundup

编辑:答案综述

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

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

采纳答案by Ghetolay

Get element position without considering any transformation on the element and up the DOM tree :

在不考虑元素和 DOM 树上的任何转换的情况下获取元素位置:

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

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

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


Get element position without considering transformation applied to it but keeping any transformation up the DOM tree.

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

To do so you could try to revert the transform.
You must first set transform-originto 0,0,0and surround yourself your transformation (scale, rotate) width translate(50%,50%) ... translate(-50%, -50%). Here is an example,
change that :

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

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

into

进入

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

We need to do that because the matrix returned by getComputedStyle() does not include stuff done with transform-origin. Don't know really why.

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

Then you can use this code :

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

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
    );    

I won't explain the math part because I think it's beyond the scope of this post. Could still explain it somewhere else (another question maybe ? ).

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

回答by ezakto

I liked Ghetolay's answer. I used it but I made it a bit more performant by avoiding the loop.

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

I have a draggable tag cloud and I have to update the drag position using transforms, but keep track of the original position (without transform).

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

The previous answer suggested to loop thru the offsetParents. In my case, and I think in a lot of cases, the tags are transformed but the container isn't. So I only have to get the first offsetParent and use getBoundingClientRect()there. No need to keep looping. I solved it doing this:

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

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

回答by Adam Leggett

The answer above that inverts the transform mathematically is a nice try, but not quite correct (and more complex than it needs to be). A more correct inversion is below.

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

This doesn't account for skew or rotate translations, but at least it produces correct edge positions when scale is used and does not impose much of a performance penalty when there is no transform.

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

It produces accurate results even with scale(0) (albeit losing subpixel precision on width/height).

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

Note that iOS with the software keyboard open produces different results between getBoundingClientRect()and offsetTop/offsetLeft- and the latter do not support subpixel precision on any browser. This produces results consistent with getBoundingClientRect().

请注意,打开软件键盘的 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;
}
<div>Hello</div>