clientX/clientY

Attempting to use clientX/clientY using root as the origin of coordinates.

by David Iglesias

HTML

<div id="transform">

  <div id="root">
  Root
    <div id="wrapper">
      <div id="content">
        Content!<br />
        300 x 300<br />
        (after scale(1.5))<br/>
        <br/>
        <tti>&lt;tt&gt;INLINE&lt;/tt&gt;</tti>
        <tti>&lt;tt&gt;INLINE&lt;/tt&gt;</tti>
        <ttb>&lt;tt&gt;BLOCK&lt;/tt&gt;</ttb>
        <ttib>&lt;tt&gt;INLINE-BLOCK&lt;/tt&gt;</ttib>
        <tti>&lt;tt&gt;INLINE&lt;/tt&gt;</tti>
      </div>
    </div>
  </div>

<div id="out"></div>

CSS

* {
  box-sizing: border-box;
  font-family: sans-serif;
}

body {
  margin: 0;
  padding: 0;
}

ttb,
ttib,
tti {
  font-family: monospace;
  font-style: italic;
  cursor: default;
}

#content ttb {
  display: block;
  background: red;
  transform-origin: 0 0 0;
  transform: translate(60px, 130px) rotateX(60deg) rotateZ(-45deg);
}

#content ttib {
  display: inline-block;
  background: yellow;
  transform-origin: 0 0 0;
  transform: translate(60px, 130px) rotateX(60deg) rotateZ(-45deg);
}

#content tti {
  display: inline;
  background: green;
  transform-origin: 0 0 0;
  transform: translate(60px, 130px) rotateX(60deg) rotateZ(-45deg);
}

#transform {
  transform-origin: 20 50;
  transform: translate(120px, 20px) rotate(30deg);
}

#root {
  position: relative;
  width: 500px;
  height: 300px;
  border: 1px solid black;
  background: #eee;
}

#wrapper {
  background: rgba(255, 127, 0, 0.5);
  pointer-events: none;
}

#content {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: auto;
  transform-origin: 0 0;
  transform: translate(90px, 10px) rotate(10deg) scale(1.5);
  background: #fabada;
  border: 1px solid red;
  width: 200px;
  height: 200px;
}

#out {
  padding: 0;
  position: absolute;
  pointer-events: none;
  width: 20px;
  height: 20px;
  background: #09f;
  font-size: 20px;
}

JavaScript

function log(n, x, y) {
  out.style.left = `${x}px`;
  out.style.top = `${y}px`;
}

let originEl = root; // origin of coordinates (glassPane).

root.addEventListener("pointermove", function (e) {
  console.log("===========================");
  console.log("===========================");
  console.log("===========================");
  console.log(`EVENT: [${e.offsetX}, ${e.offsetY}]`);
  const matrix = getElementTransformUpTo(e.target, originEl);
  const transformedOffset = matrix.transformPoint(
    new DOMPoint(e.offsetX, e.offsetY)
  );

  log(e.target.id, transformedOffset.x, transformedOffset.y);
});

function getElementTransformUpTo(element, upto) {
  let matrix = createIdentity();

  while (element != null && element !== upto && upto.contains(element)) {
    if (doesTransformApply(element)) {
      const currentMatrix = new DOMMatrix(window.getComputedStyle(element).transform);

      // Apply element offsets that are not part of transform.
      currentMatrix.e += element.offsetLeft;
      currentMatrix.f += element.offsetTop;

      console.log(getOpeningHtml(element));
      console.log(`offset: {${element.offsetLeft}, ${element.offsetTop}}`);
      console.log("transform:", window.getComputedStyle(element).transform);
      console.log("matrix:", currentMatrix.toString());
      console.log("-------------------------");

      if (!currentMatrix.isIdentity) {
        matrix.preMultiplySelf(currentMatrix);
      }
    }
    element = element.offsetParent;
  }
  return matrix;
}

function createIdentity() {
  return new DOMMatrix([1, 0, 0, 1, 0, 0]);
}

function doesTransformApply(element) {
  // Transforms don't affect inline elements, so we shouldn't take them
  // into account.
  return window.getComputedStyle(element).display !== "inline";
}

function getOpeningHtml(element) {
  const outer = element.outerHTML;
  const end = outer.indexOf(">");
  // Use {} instead of <> because jsfiddle console escapes them into &gt; and &lt;
  return "{" +...