getBoundingClientRect()
returns the coordinates of an element on the screen after being transformed. How do I calculate those coordinates before being transformed? i.e. without transforms.
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
Is there a better way?
That 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's margin)
The result of element.getBoundingClientRect().top
will be 20 (excluding the page's margin)
http://jsbin.com/kimaxojufe/1/edit?css,js,console,output
getBoundingClientRect() returns the coordinates of an element on the screen after being transformed.
The Element. getBoundingClientRect() method returns a DOMRect object providing information about the size of an element and its position relative to the viewport.
The getBoundingClientRect() method returns the size of an element and its position relative to the viewport. The getBoundingClientRect() method returns a DOMRect object with eight properties: left, top, right, bottom, x, y, width, height.
getBoundingClientRect() gives a result relative to the viewport's top-left corner ( 0,0 ), not relative to an element's parent, whereas el. offsetTop , el. offsetLeft (etc.) give a result relative to the parent.
Get element position without considering any transformation on the element and up the DOM tree :
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.
To do so you could try to revert the transform.
You must first set transform-origin
to 0,0,0
and surround yourself your transformation (scale, rotate) width translate(50%,50%) ... translate(-50%, -50%)
.
Here is an example,
change that :
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.
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 ? ).
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).
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()
.
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>
I liked Ghetolay's answer. I used it but I made it a bit more performant by avoiding the loop.
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:
var el = element;
var parentRect = element.offsetParent.getBoundingClientRect();
var offsetLeft = parentRect.left + element.offsetLeft;
var offsetTop = parentRect.top + element.offsetTop;
Just wrap the content of your element by a single div and apply the transformations to it:
<div id="element"><div class="content"></div></div>
<style> #element .content { transform: translateY(20px); }</style>
<script>
var element = document.getElementById("element");
var transformedOffset = element.children[0].getBoundingClientRect().top;
var untransformedOffset = element.getBoundingClientRect().top;
</script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With