Skip to content

Instantly share code, notes, and snippets.

@abernier
Last active April 18, 2024 18:29
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save abernier/97a5fb8c1bebacd1958e to your computer and use it in GitHub Desktop.
Save abernier/97a5fb8c1bebacd1958e to your computer and use it in GitHub Desktop.
domvertices.js
node_modules/

Compute the 4 vertices a,b,c,d absolute coordinates of any, deep, transformed, positioned DOM element.

a                b
 +--------------+
 |              |
 |      el      |
 |              |
 +--------------+
d                c

Usage

var el = document.getElementById('foo');

var v = domvertices(el);

console.log(v);

outputs:

{
	a: {x: , y: , z: },
	b: {x: , y: , z: },
	c: {x: , y: , z: },
	d: {x: , y: , z: }
}

jQuery plugin

var $el = $('#foo');
var v = $el.domvertices();

console.log(v);

you can also trace vertices for debug purpose:

v.update().trace();

or remove the trace:

v.erase();
(function () {
function Vector3() {
this.x = 0;
this.y = 0;
this.z = 0;
}
Vector3.prototype.set = function (x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
Vector3.prototype.applyMatrix4 = function (m) {
// See: https://github.com/mrdoob/three.js/blob/r92/src/math/Vector3.js#L278
var x = this.x;
var y = this.y;
var z = this.z;
var w = 1 / ( m.m14 * x + m.m24 * y + m.m34 * z + m.m44 );
this.x = ( m.m11 * x + m.m21 * y + m.m31 * z + m.m41 ) * w;
this.y = ( m.m12 * x + m.m22 * y + m.m32 * z + m.m42 ) * w;
this.z = ( m.m13 * x + m.m23 * y + m.m33 * z + m.m43 ) * w;
return this;
}
var CSSMatrix = window.CSSMatrix || window.WebKitCSSMatrix || window.MozCSSMatrix || window.MSCSSMatrix;
function relAbsOrFixedPositionnedAncestor(el) {
/*var parent = el.parentNode;
var parentComputedStyle;
while (parent && parent.nodeType === 1) { // walk up
parentComputedStyle = getComputedStyle(parent, null);
if (parentComputedStyle.position === 'relative' || parentComputedStyle.position === 'absolute' || parentComputedStyle.position === 'fixed') {
break; // stop
}
parent = parent.parentNode;
}
if (parent === document) return document.body; // html is the last element before document
return parent;*/
return el.offsetParent;
}
function domvertices(el, options) {
options || (options = {});
options.root || (options.root = document.body.parentNode) // <html> element by default
//
// a b
//  +--------------+
//  | |
//  | el |
//  | |
//  +--------------+
// d c
//
var w = el.offsetWidth;
var h = el.offsetHeight;
var v = {
a: new Vector3().set(0, 0, 0), // top left corner
b: new Vector3().set(w, 0, 0), // top right corner
c: new Vector3().set(w, h, 0), // bottom right corner
d: new Vector3().set(0, h, 0) // bottom left corner
};
//
// Walk the DOM up, and extract
//
var matrices = [];
while (el.nodeType === 1 && el !== options.root) {(function () {
var nextParent = el.parentNode;
var computedStyle = getComputedStyle(el, null);
//
// P(0->1) : relative position (to parent)
//
var P01;
(function () {
var offsetLeft = el.offsetLeft;
var offsetTop = el.offsetTop;
var position = computedStyle.position;
if (position === 'absolute' || position === 'fixed') {
// nothing
} else if (position === 'static' || position === 'relative') {
var closestPositionedParent = relAbsOrFixedPositionnedAncestor(el);
if (closestPositionedParent === el.parentNode) {
// nothing
} else {
var parent = el.parentNode;
if (parent && parent.nodeType === 1) {
offsetLeft -= parent.offsetLeft;
offsetTop -= parent.offsetTop;
}
}
}
//console.log(offsetLeft, offsetTop);
x = offsetLeft + el.clientLeft;
y = offsetTop + el.clientTop;
if (el !== document.body) {
x -= el.scrollLeft;
y -= el.scrollTop;
}
P01 = new CSSMatrix().translate(x, y, 0);
}).call(this);
//
// P(1->2) : transform-origin
//
var P12;
(function () {
var transformOrigin = computedStyle.transformOrigin || computedStyle.webkitTransformOrigin || computedStyle.MozTransformOrigin || computedStyle.msTransformOrigin;
transformOrigin = transformOrigin.split(' ');
var x = parseFloat(transformOrigin[0], 10);
var y = parseFloat(transformOrigin[1], 10);
P12 = new CSSMatrix().translate(x, y, 0);
}).call(this);
//
// P(2->3) : transform
//
var P23;
(function () {
var transform = computedStyle.transform || computedStyle.webkitTransform || computedStyle.MozTransform || computedStyle.msTransform;
P23 = new CSSMatrix(transform);
}).call(this);
//
// P(0->3) = P(0->1) . P(1->2) . P(2->3)
//
var P21 = P12.inverse();
var P03 = new CSSMatrix();
P03 = P03.multiply(P01); // (1): translate position
P03 = P03.multiply(P12); // (2): translate transform-origin
P03 = P03.multiply(P23); // (3): transform
P03 = P03.multiply(P21); // (4): inverse of (2)
matrices.push(P03);
el = nextParent;
}())}
//
// Compute the global matrix (reverse order)
//
var globalMatrix = new CSSMatrix();
var l = matrices.length;
while (l--) {
globalMatrix = globalMatrix.multiply(matrices[l]);
}
v.matrix = globalMatrix; // save the global matrix to v
v.a = v.a.applyMatrix4(globalMatrix);
v.b = v.b.applyMatrix4(globalMatrix);
v.c = v.c.applyMatrix4(globalMatrix);
v.d = v.d.applyMatrix4(globalMatrix);
return v;
}
// Exports
this.domvertices = domvertices;
if (typeof module !== "undefined" && module !== null) {
module.exports = this.domvertices;
}
}).call(this);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.scene {perspective:250px;}
.scene, .camera {width:100vw; height:100vh;}
.camera, .camera * {transform-style:preserve-3d;}
.camera {
transform:rotateX(15deg) rotateY(5deg) scale(1.1); background:rgba(255,255,255,.5);
}
div {
display: inline-block;
background: rgba(255,0,0,.5);
width: 100px;height:50px;
box-shadow: 0 0 0 1px pink inset;
}
#a {
position:relative; margin-left:50px;margin-top:100px;
transform:translate(200px, 150px) rotate(-10deg) translateZ(50px); transform-origin:center;
}
#b {
margin-left:100px;
transform:translate(100px, 50px) rotate(-10deg) translateZ(50px) rotateX(-20deg); transform-origin:center;
}
#c {
/*transform:translateY(100px);*/
}
#d {
margin-top:10px; margin-left:50px;
}
#e {
transform:translateY(30px);
}
#f {
position:absolute;left:100px;top:50px;
transform:translate(-50px, 50px) rotate(5deg); transform-origin:right top;
}
#g {
position:fixed;left:100px;top:50px;
transform:scale(2) translate(-60px, 30px) rotate(20deg); transform-origin:right top;
}
</style>
</head>
<body style="margin:0;">
<div class="scene"><div class="camera">
<div id="a">
relative;A;(50,100);t(200,150)r(10)tz(50);center
<div id="b">
static;B;(100,0);t(100,50)r(-10)tz(50)rx(-20);center
<div id="c">
static;C;(0,0);;center
<div id="d">
static;D;(50,10);;center
<div id="e">
static;E;(0,0);ty(30);center
<div id="f">
absolute;F;(100,50);t(-50,50)r(5);right top
<div id="g">
fixed;G(100,50);s(2)t(-60,30)r(20);right top
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div></div>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="domvertices.js"></script>
<script src="jquery.domvertices.js"></script>
<script>
var $els = $('#a, #b, #c, #d, #e, #f, #g');
$.fn.domvertices.defaults = {append: $('.scene')};
$els.each(function (i, el) {
$(el).domvertices().trace();
});
$(window).resize(function () {
$.fn.domvertices.v.forEach(function (v) {
v.update();
v.trace();
});
});
</script>
</body>
</html>
(function () {
var $ = this.jQuery || require('jquery');
var domvertices = this.domvertices || require('domvertices');
function V(el, options) {
options || (options = {});
this.options = $.extend({}, $.fn.domvertices.defaults, options);
this.$el = $(el);
this.el = this.$el[0];
this.update();
}
V.prototype.update = function () {
var v = domvertices(this.el, {root: this.options.root});
this.a = v.a;
this.b = v.b;
this.c = v.c;
this.d = v.d;
this.matrix = v.matrix;
return this;
};
V.prototype.trace = function () {
var position = {
display:'block',width:'0px',height:'0px', boxShadow:'0 0 0 3px lime',
position:'absolute',left:'0px',top:'0px'
};
this.$a || (this.$a = $('<div>').appendTo(this.options.append).css(position));
this.$b || (this.$b = $('<div>').appendTo(this.options.append).css(position));
this.$c || (this.$c = $('<div>').appendTo(this.options.append).css(position));
this.$d || (this.$d = $('<div>').appendTo(this.options.append).css(position));
var v = {
a: this.a,
b: this.b,
c: this.c,
d: this.d
};
for (k in v) {
this['$'+k].css({transform:'translate3d(' + v[k].x + 'px,' + v[k].y + 'px, ' + v[k].z + 'px)'});
}
return this;
};
V.prototype.erase = function () {
this.$a.remove();
this.$b.remove();
this.$c.remove();
this.$d.remove();
this.$a = undefined;
this.$b = undefined;
this.$c = undefined;
this.$d = undefined;
return this;
};
var arr = [];
$.fn.domvertices = function (options) {
var v = new V(this[0], options);
arr.push(v);
this.data('v', v);
return v;
};
$.fn.domvertices.v = arr;
$.fn.domvertices.defaults = {
append: document.body,
root: document.body.parentNode
};
}).call(this);
{
"name": "domvertices",
"version": "0.0.2",
"description": "Compute 4 vertices absolute coordinates from any DOM element.",
"main": "domvertices.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gist.github.com/97a5fb8c1bebacd1958e.git"
},
"author": "Antoine BERNIER (abernier)",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"jquery": "^3.3.1"
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
/*.scene {perspective:250;}
.scene, .camera {width:100vw; height:100vh;}*/
/*.camera, .camera * {transform-style:preserve-3d;}*/
.camera {
/*transform:rotateX(20deg) scale(1.1); background:rgba(255,255,255,.5);*/
}
div {
display: inline-block;
background: rgba(255,0,0,.5);
width: 100px;height:50px;
box-shadow: 0 0 0 1px pink inset;
}
#a {
position:relative; margin-left:50px;margin-top:100px;
transform:translate(200px, 150px) rotate(-10deg) translateZ(50px); transform-origin:center;
}
#b {
margin-left:100px;
transform:translate(100px, 50px) rotate(-10deg) translateZ(50px) rotateX(-20deg); transform-origin:center;
}
#c {
/*transform:translateY(100px);*/
}
#d {
margin-top:10px;
}
#e {
transform:translateY(100px);
}
#f {
position:absolute;left:100px;top:100px;
transform:translate(-50px, 50px) rotate(5deg); transform-origin:right top;
}
#g {
position:fixed;left:100px;top:50px;
transform:scale(2) translate(-60px, 30px) rotate(20deg); transform-origin:right top;
}
</style>
</head>
<body style="margin:0;">
<!--<div class="scene"><div class="camera">-->
<div id="a">
relative;A;(50,100);t(200,150)r(10)tz(50);center
<div id="b">
static;B;(100,50);t(100,50)r(-10)tz(50)rx(-20);center
<div id="c">
static;C;(0,0);;center
<div id="d">
static;D;(0,0);;center
<div id="e">
static;E;(0,0);;center
<div id="f">
absolute;F;(100,100);t(-50,50)r(5);right top
<div id="g">
fixed;G(100,50);s(2)t(-60,30)r(20);right top
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--</div></div>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore.js"></script>
<script src="https://code.jquery.com/jquery-2.1.3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.js"></script>
<script src="domvertices.js"></script>
<script>
function trace(v) {
for (k in v) { //
$('<b>').css({
display:'block',width:'0px',height:'0px', boxShadow:'0 0 0 3px lime',
position:'absolute',left:'0px',top:'0px', transform:'translate3d(' + v[k].x + 'px,' + v[k].y + 'px, ' + v[k].z + 'px)'
}).appendTo('body'); // !important so <b> is subjected to perspective
}
}
function normalizeMatrix (matrixString) {
var c = matrixString.split(/\s*[(),]\s*/).slice(1,-1);
var matrix;
if (c.length === 6) {
// 'matrix()' (3x2)
matrix = new THREE.Matrix4().set(
c[0], +c[2], 0, +c[4],
+c[1], +c[3], 0, +c[5],
0, 0, 1, 0,
0, 0, 0, 1
);
} else if (c.length === 16) {
// matrix3d() (4x4)
matrix = new THREE.Matrix4().set(
+c[0], +c[4], +c[8], +c[12],
+c[1], +c[5], +c[9], +c[13],
+c[2], +c[6], +c[10], +c[14],
+c[3], +c[7], +c[11], +c[15]
);
} else {
// handle 'none' or invalid values.
matrix = new THREE.Matrix4().identity();
}
return matrix;
}
function m(el, o) {
var computedStyle = getComputedStyle(el, null);
var transformOrigin = computedStyle.transformOrigin.split(' ').map(function (el) {return parseFloat(el, 10);});
var P01 = new THREE.Matrix4().makeTranslation(o.offset[0], o.offset[1], 0);
var P12 = new THREE.Matrix4().makeTranslation(transformOrigin[0], transformOrigin[1], 0);
var P23 = normalizeMatrix(computedStyle.transform);
var P21 = new THREE.Matrix4().getInverse(P12);
var P03 = new THREE.Matrix4().identity()
.multiply(P01) // (1): translate position
.multiply(P12) // (2): translate transform-origin
.multiply(P23) // (3): transform
.multiply(P21) // (4): inverse of (2)
;
return P03;
}
function v(el) {
var w = el.offsetWidth;
var h = el.offsetHeight;
var v = {
a: new THREE.Vector3().set(0, 0, 0), // top left corner
b: new THREE.Vector3().set(w, 0, 0), // top right corner
c: new THREE.Vector3().set(w, h, 0), // bottom right corner
d: new THREE.Vector3().set(0, h, 0) // bottom left corner
};
return v;
}
// a
var a = document.querySelector('#a');
var ma = m(a, {
offset: [50, 100]
});
var va = v(a);
//trace(va);
/*va.a = va.a.applyMatrix4(ma);
va.b = va.b.applyMatrix4(ma);
va.c = va.c.applyMatrix4(ma);
va.d = va.d.applyMatrix4(ma);
trace(va);*/
// b
var b = document.querySelector('#b');
var mb = m(b, {
offset: [100, 32]
});
var vb = v(b);
//trace(vb);
/*vb.a = vb.a.applyMatrix4(mb);
vb.b = vb.b.applyMatrix4(mb);
vb.c = vb.c.applyMatrix4(mb);
vb.d = vb.d.applyMatrix4(mb);
trace(vb);
vb.a = vb.a.applyMatrix4(ma);
vb.b = vb.b.applyMatrix4(ma);
vb.c = vb.c.applyMatrix4(ma);
vb.d = vb.d.applyMatrix4(ma);
trace(vb);*/
// c
var c = document.querySelector('#c');
var mc = m(c, {
offset: [0, 32]
});
var vc = v(c);
/*vc.a = vc.a.applyMatrix4(mc);
vc.b = vc.b.applyMatrix4(mc);
vc.c = vc.c.applyMatrix4(mc);
vc.d = vc.d.applyMatrix4(mc);
trace(vc);
vc.a = vc.a.applyMatrix4(mb);
vc.b = vc.b.applyMatrix4(mb);
vc.c = vc.c.applyMatrix4(mb);
vc.d = vc.d.applyMatrix4(mb);
trace(vc);
vc.a = vc.a.applyMatrix4(ma);
vc.b = vc.b.applyMatrix4(ma);
vc.c = vc.c.applyMatrix4(ma);
vc.d = vc.d.applyMatrix4(ma);
trace(vc);*/
// d
var d = document.querySelector('#d');
var md = m(d, {
offset: [0, 42]
});
var vd = v(d);
/*vd.a = vd.a.applyMatrix4(md);
vd.b = vd.b.applyMatrix4(md);
vd.c = vd.c.applyMatrix4(md);
vd.d = vd.d.applyMatrix4(md);
trace(vd);
vd.a = vd.a.applyMatrix4(mc);
vd.b = vd.b.applyMatrix4(mc);
vd.c = vd.c.applyMatrix4(mc);
vd.d = vd.d.applyMatrix4(mc);
trace(vd);
vd.a = vd.a.applyMatrix4(mb);
vd.b = vd.b.applyMatrix4(mb);
vd.c = vd.c.applyMatrix4(mb);
vd.d = vd.d.applyMatrix4(mb);
trace(vd);
vd.a = vd.a.applyMatrix4(ma);
vd.b = vd.b.applyMatrix4(ma);
vd.c = vd.c.applyMatrix4(ma);
vd.d = vd.d.applyMatrix4(ma);
trace(vd);*/
// e
var e = document.querySelector('#e');
var me = m(e, {
offset: [0, 32]
});
var ve = v(e);
/*ve.a = ve.a.applyMatrix4(me);
ve.b = ve.b.applyMatrix4(me);
ve.c = ve.c.applyMatrix4(me);
ve.d = ve.d.applyMatrix4(me);
trace(ve);
ve.a = ve.a.applyMatrix4(md);
ve.b = ve.b.applyMatrix4(md);
ve.c = ve.c.applyMatrix4(md);
ve.d = ve.d.applyMatrix4(md);
trace(ve);
ve.a = ve.a.applyMatrix4(mc);
ve.b = ve.b.applyMatrix4(mc);
ve.c = ve.c.applyMatrix4(mc);
ve.d = ve.d.applyMatrix4(mc);
trace(ve);
ve.a = ve.a.applyMatrix4(mb);
ve.b = ve.b.applyMatrix4(mb);
ve.c = ve.c.applyMatrix4(mb);
ve.d = ve.d.applyMatrix4(mb);
trace(ve);
ve.a = ve.a.applyMatrix4(ma);
ve.b = ve.b.applyMatrix4(ma);
ve.c = ve.c.applyMatrix4(ma);
ve.d = ve.d.applyMatrix4(ma);
trace(ve);*/
// f
var f = document.querySelector('#f');
var mf = m(f, {
offset: [100, 100]
});
var vf = v(f);
/*vf.a = vf.a.applyMatrix4(mf);
vf.b = vf.b.applyMatrix4(mf);
vf.c = vf.c.applyMatrix4(mf);
vf.d = vf.d.applyMatrix4(mf);
trace(vf);
vf.a = vf.a.applyMatrix4(me);
vf.b = vf.b.applyMatrix4(me);
vf.c = vf.c.applyMatrix4(me);
vf.d = vf.d.applyMatrix4(me);
trace(vf);
vf.a = vf.a.applyMatrix4(md);
vf.b = vf.b.applyMatrix4(md);
vf.c = vf.c.applyMatrix4(md);
vf.d = vf.d.applyMatrix4(md);
trace(vf);
vf.a = vf.a.applyMatrix4(mc);
vf.b = vf.b.applyMatrix4(mc);
vf.c = vf.c.applyMatrix4(mc);
vf.d = vf.d.applyMatrix4(mc);
trace(vf);
vf.a = vf.a.applyMatrix4(mb);
vf.b = vf.b.applyMatrix4(mb);
vf.c = vf.c.applyMatrix4(mb);
vf.d = vf.d.applyMatrix4(mb);
trace(vf);
vf.a = vf.a.applyMatrix4(ma);
vf.b = vf.b.applyMatrix4(ma);
vf.c = vf.c.applyMatrix4(ma);
vf.d = vf.d.applyMatrix4(ma);
trace(vf);*/
// g
var g = document.querySelector('#g');
var mg = m(g, {
offset: [100, 50]
});
var vg = v(g);
vg.a = vg.a.applyMatrix4(mg);
vg.b = vg.b.applyMatrix4(mg);
vg.c = vg.c.applyMatrix4(mg);
vg.d = vg.d.applyMatrix4(mg);
trace(vg);
vg.a = vg.a.applyMatrix4(mf);
vg.b = vg.b.applyMatrix4(mf);
vg.c = vg.c.applyMatrix4(mf);
vg.d = vg.d.applyMatrix4(mf);
trace(vg);
vg.a = vg.a.applyMatrix4(me);
vg.b = vg.b.applyMatrix4(me);
vg.c = vg.c.applyMatrix4(me);
vg.d = vg.d.applyMatrix4(me);
trace(vg);
vg.a = vg.a.applyMatrix4(md);
vg.b = vg.b.applyMatrix4(md);
vg.c = vg.c.applyMatrix4(md);
vg.d = vg.d.applyMatrix4(md);
trace(vg);
vg.a = vg.a.applyMatrix4(mc);
vg.b = vg.b.applyMatrix4(mc);
vg.c = vg.c.applyMatrix4(mc);
vg.d = vg.d.applyMatrix4(mc);
trace(vg);
vg.a = vg.a.applyMatrix4(mb);
vg.b = vg.b.applyMatrix4(mb);
vg.c = vg.c.applyMatrix4(mb);
vg.d = vg.d.applyMatrix4(mb);
trace(vg);
vg.a = vg.a.applyMatrix4(ma);
vg.b = vg.b.applyMatrix4(ma);
vg.c = vg.c.applyMatrix4(ma);
vg.d = vg.d.applyMatrix4(ma);
trace(vg);
</script>
</body>
</html>
@abernier
Copy link
Author

@yckart
Copy link

yckart commented Apr 20, 2016

THREE could be replaced by CSSMatrix (w3.org) ;)

@abernier
Copy link
Author

@yckart now free of any deps as of v0.0.2 ;)

@abernier
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment