获取DOM元素的精确位置,递归包括父级的旋转和平移。

huangapple go评论83阅读模式
英文:

Get DOM element exact position recursively including parent rotations and translations

问题

我尝试创建一个公式,允许我获取DOM元素在最高祖先标志中的确切位置。我的问题有一些特定的规则。每个元素都是绝对定位的,并且每个元素都有已知的像素顶部和左侧值。每个元素还可以具有已知的角度旋转变换,旋转变换也是从元素的左上角执行的。

为了更容易理解,下面有一个图像示例。

在这个示例中,我们有一个由3个元素组成的树状结构。元素C是元素B的子元素,元素B是元素A的子元素。为了尽可能简单,将这3个元素视为简单的<div>标记。这些元素中的每一个都在其父标志中具有已知的位置和旋转角度。

问题是,我知道每个div元素在其直接父标志中的顶部和左侧值。但我想找到元素C在表示为元素A的标志的总标志中的左上角位置。

为了实现这一点,我使用了齐次变换矩阵。我计算了每个元素的总变换矩阵,将平移和旋转矩阵组合在一起。

元素的旋转矩阵如下所示:

获取DOM元素的精确位置,递归包括父级的旋转和平移。

其中t是给定元素在其父标志中的弧度角。

元素的平移矩阵如下所示:

获取DOM元素的精确位置,递归包括父级的旋转和平移。

其中top和left分别是元素相对于其父标志左上角的偏移量。

让我们称之为Mr.x元素x的旋转矩阵。

让我们称之为Mt.x元素x的平移矩阵。

让我们称之为Mg.x元素x的全局变换矩阵,通过执行Mr.x * Mt.x获得。

让我们称之为Cx.y元素x在y元素标志中的左上角坐标。

计算如下:

所以我知道Cc.bCb.aCa.g,其中g是总标志。我想要Cc.g或元素C在总标志中的左上角坐标。

因此,我首先累积了所有父元素的变换矩阵,从三维单位矩阵开始。让我们称这个矩阵为Gtm,表示总变换矩阵。

计算如下:

Gtm = Gtm * Mg.a

Gtm = Gtm * Mg.b

然后,我创建了一个具有元素C在元素B中的偏移量的“齐次”点,如下所示:

p = (6, 1.5, 0)

然后,我将我的点与总变换矩阵Gtm相乘,以在总标志中获取该点。

point = p * Gtm。

但给定的点不正确,也没有正确地整合所有父元素的偏移量,我不知道我在这里错过了什么。

对不起,我的数学和表示可能不够精确。如果我不够清楚,请随时问我以获取更多信息,感谢您在此上花费的任何时间。

英文:

Context

I'm trying to create a formula that allow me to get the exact position of a DOM element in the highest ancestor landmark. There are some specific rules in my probleme. Each element are absolute positionned and all element have a known top and left value in pixels. Each element can also have a rotation transform with a known value in degres. The rotation transform is also performed from the top left corner of the element.

To make it more understandable there is an exemple as an image just below.

获取DOM元素的精确位置,递归包括父级的旋转和平移。

In this exemple we have an arborescence composed of 3 elements. The element C is child of element B and element B is child of element A. To make it as simple as possible consider those 3 elements as simple &lt;div&gt; tag. Each of these elements have a known position and rotation angle in the parent landmark.

The probleme

I know the top and left value of each div element in its direct parent landmark. But i want to find the top left corner position of the element C in the general landmark representated as the landmark of the element A.

To achieve that i used homogenous transformation matrix. I calculate the general transform matrix of each element combining the translation and rotation matrix.

The rotation matrix of an element explained as :

获取DOM元素的精确位置,递归包括父级的旋转和平移。

With t the angle in radient of a given element in its parent landmark.

The translation matrix of an element explained as :

获取DOM元素的精确位置,递归包括父级的旋转和平移。

With top, left respectively the offset of the element from its parent origin top left corner.

Let's call Mr.x the rotation matrix of the element x.

Let's call Mt.x the translation matrix of the element x.

Let's call Mg.x the global transformation matrix of the element x. Obtained by doing Mr.x * Mt.x.

Let's call Cx.y the coordinates of the top left element x in the y element landmark.

The calcul

So i know Cc.b, Cb.a and Ca.g with g the general landmark. And i want Cc.g or the coordinates of the top left of element C in the general landmark.

So what i first did is accumulating all the transformation matrix of all parents elements starting with a 3 dimensions identity matrix. let's call this matrix Gtm for general transformation matrix.

The calcul is :

Gtm = Gtm * Mg.a

Gtm = Gtm * Mg.b

Then i create a "homogenous" point with the offset of the element C in the element B like below:

p = (6, 1.5, 0)

Then i multiply my point with the general transform matrix Gtm to get this point in the general landmark.

point = p * Gtm.

But that given point isn't correct and isn't integrating all the offsets of the parent correctly i don't get the correct coordinates and i don't know what i'm missing on here.

Sorry my Math and representations are approximatives. Ask me anything to get more informations if i'm not clear enough. And thank for any time spent on this.

Edit 1

Consider a Js object which a reference to a dom element and a reference to it's parent object.

let elA = {
  DOM_element = /* The &lt;div&gt; A */
  parent = null;
}
let elB = {
  DOM_element = /* The &lt;div&gt; B */
  parent = elA;
}
let elC = {
  DOM_element = /* The &lt;div&gt; C */
  parent = elB;
}

Consider that each of these objects have three methods respectively called getTranslationMatrix, getRotationMatrix, and getTransformMatrix, and that each of these objects have an attribute named rotation with a rotation value in degree

The code :

  /**
  * Get the translation matrix of the element 
  */
  getTranslationMatrix() {
    // Create the translation matrix of the element
    let translationMatrix = matrix([
      [1, 0, this.DOM_element.offsetLeft],
      [0, 1, this.DOM_element.offsetTop],
      [0, 0, 1],
    ]);

    return translationMatrix;
  }

  /**
  * Get the rotation matrix of the element 
  */
  getRotationMatrix() {
    // Create the rotation matrix of the element
    let rotationMatrix = matrix([
      [Math.cos(this.rotation * (Math.PI/180)), Math.sin(this.rotation * (Math.PI/180)), 0],
      [-Math.sin(this.rotation * (Math.PI/180)), Math.cos(this.rotation * (Math.PI/180)), 0],
      [0, 0, 1],
    ]);

    return rotationMatrix;
  }

  /**
  * Get the transform matrix of the element 
  */
  getTransformMatrix() {
    // Multiply all transform matrixes
    return multiply(this.getRotationMatrix(), this.getTranslationMatrix());
  }

matrix and multiply are fonctions provided by the mathjs library.

Now the code executed in the object elC :

// Get the reversed context stack
    let contextStack = [];
    let context = this.parent;
    while(context) {
      contextStack.unshift(context);
      context = context.parent;
    }

    // Create the transform matrix
    let transformMatrix = matrix([
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1]
    ]);
    contextStack.forEach(context =&gt; {
      transformMatrix = multiply(context.getTransformMatrix(), transformMatrix);
    });

    // Create the origin point
    let origin = [
      this.DOM_element.offsetLeft,
      this.DOM_element.offsetTop,
      1
    ];

    // Apply the transform matrix
    origin = multiply(origin, transformMatrix);

And i should get the correct coordinates in the origin point but that is not the case.

答案1

得分: 1

以下是翻译好的内容:

我们可以通过使用 computed transform 属性来获取 "c" 元素及其所有祖先的组合旋转矩阵,然后进行 matrix multiplying 这些矩阵(注意 CSS 使用 齐次坐标表示法,而不是像通常情况下需要执行线性代数时我们所做的方式来指定 2D RT 矩阵为 3x3 矩阵)。

这将得到总旋转,以及一个 "错误的" 位移,但无论如何,我们将将其设置为零,因为我们可以使用 getBoundingClientRect 来获取所需的真实位移。

此代码块获取了元素 "c" 并计算了相应的绝对变换,然后叠加了一个新的 "d" 元素(绝对定位于页面本身),覆盖在 "c" 元素之上。在此代码片段中,"c" 元素的背景设置为明亮红色,"d" 元素的背景设置为淡蓝色,因此如果一切顺利,您应该会看到 "d" 元素叠加在 "c" 元素上,并呈紫色。

还请注意,每个框的顶部都被突出显示为粉色,以突出显示应用的旋转。

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->
// 确保 "d" 元素具有与 "c" 元素相同的尺寸
const cstyle = getComputedStyle(c);
const h = parseFloat(cstyle.height);
const w = parseFloat(cstyle.width);
d.style.height = `${h}px`;
d.style.width = `${w}px`;

// 然后,使用它们的中心点将 "d" 元素对齐到 "c" 元素
const bbox = c.getBoundingClientRect();
const cy = bbox.top + bbox.height/2;
const cx = bbox.left + bbox.width/2;
d.style.top = `${cy - h/2}px`;
d.style.left = `${cx - w/2}px`;

// 最后,计算总旋转,去除位移,并将该总旋转应用于 "d" 元素
const fullMatrix = getFullMatrix(c);
fullMatrix.e = fullMatrix.f = 0;
d.style.transform = fullMatrix;

function getFullMatrix(element) {
  if (!element) return new DOMMatrix();
  const css = getComputedStyle(element).transform;
  const matrix = new DOMMatrix(css);
  return getFullMatrix(element.offsetParent).multiply(matrix);
}

<!-- language: lang-css -->
div {
  border-top: 2px solid magenta;
}

#a {
  position:relative;
  background: lightgrey;
  width: 10em;
  height: 7em;
  padding: 20px;
  margin-left: 20px;
  margin-top: 1em;
}

#b {
  position:absolute;
  background: grey;
  width: 5em;
  height: 3em;
  top: 2em;
  left: 2em;
  transform-origin: 50% 50%;
  transform: rotate(30deg);
  padding: 10px;
  margin-top: 10px;
}

#c {
  width: 2.2em;
  height: 1.8em;
  background: red;
  transform-origin: 50% 50%;
  transform: translate(1.5em, 0.75em) rotate(127deg);
}

#d {
  position: absolute;
  background: rgba(0,0,250,0.5);
}

<!-- language: lang-html -->
<div id="a">
  <div id="b">
    <div id="c"></div>
  </div>
</div>

<div id="d"></div>

<!-- end snippet -->

请注意,当然也可以通过使用切片(0,4)来计算旋转矩阵,并计算2x2旋转矩阵的乘法,但这实际上是一种过早的优化(而且我们仍然需要添加0位移项)。如果需要的话,我们随时可以简化事物。

还请注意,IEEE 浮点数 是我们的朋友,旋转矩阵的有限小数位数以及边界框的分数像素值意味着我们可能会偏离半个像素,这可能会渲染为整个像素的偏移。不幸的是,这是这个媒体的一个后果。如果我们想要 真实 的角度和偏移,那么我们真的应该使用 CSS 变量,这样我们可以直接使用定义布局的数字,而不是逆向工作。

例如:

:root {
  --px-per-em: 16;
}

div {
  --x: 0;
  --y: 0;
  --angle: 0;
  --rotation: calc(1deg * var(--angle));
  transform:
    translate(calc(1px * var(--x)), calc(1px * var(--y)))
    rotate(var(--rotation));
}

#a {
  ...
}

#b {
  --angle: 30;
  ...
}

#c {
  --x: calc(1.5 * var(--px-per-em));
  --y: calc(0.75 * var(--px-per-em));
  --angle: 127;
  ...
}

现在,我们可以使用 getComputedStyle(element).getPropertyValue(--angle) | 0;(带有相同类型的递归)来获取我们正在使用的真实总角度,而不是通过一堆矩阵乘法逆向工作。

英文:

We can get the combined rotation matrix by using the computed transform property for the "c" element and all its ancestors, and then matrix multiplying those (bearing in mind that CSS uses homogeneous coordinate notation rather than specifying the 2D RT matrix as a 3x3 matrix the way we normally do when we need to perform linear algebra).

This gets us to the total rotation, plus a translation that's "wrong", but that we're going to set to zero anyway, because we can get the true translation we need by using getBoundingClientRect.

The takes box "c" and calculates what the corresponding absolute transform should be, and overlays a new div "d" (absolutely positioned wrt the page itself) on top of "c". In this snippet, "c" has been given a bright red background, "d" has been given a faint, transparent blue background, and so if all went well, you should see "d" overlaid on "c" with a purplish colour.

Also note that the tops of each box have been highlight in pink, to highlight the applied rotations.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// Make sure &quot;d&quot; has the same dimensions as &quot;c&quot;
const cstyle = getComputedStyle(c);
const h = parseFloat(cstyle.height);
const w = parseFloat(cstyle.width);
d.style.height = `${h}px`;
d.style.width = `${w}px`;

// Then, align &quot;d&quot; to &quot;c&quot; using their center points.
const bbox = c.getBoundingClientRect();
const cy = bbox.top + bbox.height/2;
const cx = bbox.left + bbox.width/2;
d.style.top = `${cy - h/2}px`;
d.style.left = `${cx - w/2}px`;

// And finally, compute the total rotation, strip
// the translation, and apply that total rotation to &quot;d&quot;.
const fullMatrix = getFullMatrix(c);
fullMatrix.e = fullMatrix.f = 0;
d.style.transform = fullMatrix;

function getFullMatrix(element) {
  if (!element) return new DOMMatrix();
  const css = getComputedStyle(element).transform;
  const matrix = new DOMMatrix(css);
  return getFullMatrix(element.offsetParent).multiply(matrix);
}

<!-- language: lang-css -->

div {
  border-top: 2px solid magenta;
}

#a {
  position:relative;
  background: lightgrey;
  width: 10em;
  height: 7em;
  padding: 20px;
  margin-left: 20px;
  margin-top: 1em;
}

#b {
  position:absolute;
  background: grey;
  width: 5em;
  height: 3em;
  top: 2em;
  left: 2em;
  transform-origin: 50% 50%;
  transform: rotate(30deg);
  padding: 10px;
  margin-top: 10px;
}


#c {
  width: 2.2em;
  height: 1.8em;
  background: red;
  transform-origin: 50% 50%;
  transform: translate(1.5em, 0.75em) rotate(127deg);
}

#d {
  position: absolute;
  background: rgba(0,0,250,0.5);
}

<!-- language: lang-html -->

&lt;div id=&quot;a&quot;&gt;
  &lt;div id=&quot;b&quot;&gt;
    &lt;div id=&quot;c&quot;&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div id=&quot;d&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

Note that we can of course also just compute the rotation matrices by using a slice(0,4) and computing multiplication of the 2x2 rotation matrices, but that's frankly a premature optimization (and we'll still need to set add the 0 translation terms). We can always pare things down later if needed.

Also note that IEEE floating point numbers are not our friend, and the limited decimal places of the rotation matrix, as well as fractional pixel values for bounding boxes, means that we might be half a pixel off, which can get rendered as being an entire pixel off. This is, unfortunately, a consequence of the medium. If we wanted the true angle and offset, then really we should be using CSS variables so we can work directly with the numbers that define our layout instead of working backwards.

For example:

:root {
  --px-per-em: 16;
}

div {
  --x: 0;
  --y: 0;
  --angle: 0;
  --rotation: calc(1deg * var(--angle));
  transform:
    translate(calc(1px * var(--x)), calc(1px * var(--y)))
    rotate(var(--rotation));
}

#a {
  ...
}

#b {
  --angle: 30;
  ...
}


#c {
  --x: calc(1.5 * var(--px-per-em));
  --y: calc(0.75 * var(--px-per-em));
  --angle: 127;
  ...
}

We can now use getComputedStyle(element).getPropertyValue(`--angle`) | 0; (with the same type of recursion) to get the true total angle we're using, rather than working our way back through a bunch of matrix multiplications.

huangapple
  • 本文由 发表于 2023年3月21日 01:21:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75793413.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定