How to calculate getBoundingClientRect () without conversion?

getBoundingClientRect() returns the coordinates of the element on the screen after conversion . How to calculate these coordinates before conversion ? that is, without conversion.

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 this leads to a slow breakdown of the layout, this is especially noticeable if you do this on many elements. Live demo: http://jsbin.com/nibiqogosa/1/edit?js,console,output

Is there a better way?


This JavaScript code can be applied to:

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

And the result will be 0 (excluding the page field)

The result of element.getBoundingClientRect().top will be 20 (excluding the page margin)

Edit: Review Responses

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

+10
source share
3 answers

Get the position of an element without taking into account any transformation in the element and up to the DOM tree:

 var el = element, offsetLeft = 0, offsetTop = 0; do{ offsetLeft += el.offsetLeft; offsetTop += el.offsetTop; el = el.offsetParent; } while( el ); 

Get the position of an element without considering the transformation applied to it, but storing any transformation in the DOM tree.

To do this, you can try to return the conversion.
You must first set transform-origin to 0,0,0 and surround yourself with your transformation (scale, rotation) of width translate(50%,50%) ... translate(-50%, -50%) . Here is an example
change this:

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

in

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

We need to do this because the matrix returned by getComputedStyle () does not include material created using the origin transformation. I do not know why.

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 will not explain the mathematical part, because I think this is beyond the scope of this publication. Could still explain it somewhere else (maybe another question?).

+12
source

I liked the answer of Ghetolay. I used it, but I made it a little more efficient, avoiding the loop.

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

The previous answer suggested scrolling through offsetParents. In my case, and I think that in many cases the tags are transformed, but there is no container. So I only need to get the first offsetParent and use getBoundingClientRect() there. There is no need to continue the cycle. I decided to do this:

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

The answer above, which mathematically inverts the transformation, is a good attempt, but not entirely correct (and more complicated than it should be). More correct inversion below.

This does not take into account the skew or rotation of the turn, but at least it gives the correct position of the edges when using scale and does not impose significant performance losses when there is no conversion.

It gives accurate results even at a scale of (0) (although it loses subpixel accuracy in width / height).

Please note that iOS with an open soft keyboard gives different results between getBoundingClientRect() and offsetTop / offsetLeft - and the latter do not support sub-pixel accuracy in any browser. This gives results corresponding to 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> 

+1
source

Source: https://habr.com/ru/post/1210270/


All Articles