Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
482 views
in Technique[技术] by (71.8m points)

javascript - How to get a canvas relative mouse position of a CSS 3D transformed canvas?

Just for fun I'm trying to draw on 3D transformed canvases. I wrote some code and it kind of works

const m4 = twgl.m4;

[...document.querySelectorAll('canvas')].forEach((canvas) => {
  const ctx = canvas.getContext('2d');
  let count = 0;

  canvas.addEventListener('mousemove', (e) => {
    const pos = getElementRelativeMousePosition(e, canvas);
    ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
    ctx.fillRect(pos.x - 1, pos.y - 1, 3, 3);
  });
});

function getElementRelativeMousePosition(e, elem) {
  const pos = convertPointFromPageToNode(elem, e.pageX, e.pageY); 
  
  return {
    x: pos[0],
    y: pos[1],
  };
}

function hsl(h, s, l) {
  return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}

function convertPointFromPageToNode(elem, pageX, pageY) {
  const mat = m4.inverse(getTransformationMatrix(elem));
  return m4.transformPoint(mat, [pageX, pageY, 0]);
};

function getTransformationMatrix(elem) {
  let matrix = m4.identity();
  let currentElem = elem;

  while (currentElem !== undefined && 
         currentElem !== currentElem.ownerDocument.documentElement) {
    const style = window.getComputedStyle(currentElem);
    const localMatrix = parseMatrix(style.transform);
    matrix = m4.multiply(localMatrix, matrix);
    currentElem = currentElem.parentElement;
  }

  const w = elem.offsetWidth;
  const h = elem.offsetHeight;
  let i = 4;
  let left = +Infinity;
  let top = +Infinity;
  for (let i = 0; i < 4; ++i) {
    const p = m4.transformPoint(matrix, [w * (i & 1), h * ((i & 2) >> 1), 0]);
    left = Math.min(p[0], left);
    top = Math.min(p[1], top);
  }
  const rect = elem.getBoundingClientRect()
  document.querySelector('p').textContent =
    `${w}x${h}`;
  matrix =  m4.multiply(m4.translation([
     window.pageXOffset + rect.left - left, 
     window.pageYOffset + rect.top - top,
     0]), matrix);
  return matrix;
}


function parseMatrix(str) {
  if (str.startsWith('matrix3d(')) {
    return str.substring(9, str.length - 1).split(',').map(v => parseFloat(v.trim()));
  } else if (str.startsWith('matrix(')) {
    const m = str.substring(7, str.length - 1).split(',').map(v => parseFloat(v.trim()));
    return [
      m[0], m[1], 0, 0,
      m[2], m[3], 0, 0,
      0, 0, 1, 0,
      m[4], m[5], 0, 1,
    ]
  } else if (str == 'none') {
    return m4.identity();
  }
  throw new Error('unknown format');
}
canvas { 
  display: block;
  background: yellow;
  transform: scale(0.75);
}
#c1 {
  margin: 20px;
  background: red;
  transform: translateX(-50px);
  display: inline-block;
}
#c2 {
  margin: 20px;
  background: green;
  transform: rotate(45deg);
  display: inline-block;
}
#c3 {
  margin: 20px;
  background: blue;
  display: inline-block;
}

#c4 {
  position: absolute;
  top: 0;
  background: cyan;
  transform: translateX(-250px) rotate(55deg);
  display: inline-block;
}
#c5 {
  background: magenta;
  transform: translate(50px);
  display: inline-block;
}
#c6 {
  background: pink;
  transform: rotate(45deg);
  display: inline-block;
}
<p>
foo
</p>
<div id="c1">
  <div id="c2">
    <div id="c3">
      <canvas></canvas>
    </div>
  </div>
</div>
<div id="c4">
  <div id="c5">
    <div id="c6">
      <canvas></canvas>
    </div>
  </div>
</div>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Note: That's just a complementary answer on what OP already did find by themselves.

You can actually make this all work by using the MouseEvent constructor.

You can pass the clientX and clientY properties of this Event inside the constructor (or pageX & pageY if you prefer), then dispatching this composed event to your target will set its offsetX and offsetY properties relative to the target.

Since dispatchEvent does fire the Event synchronously, we can even make a converter:

const init_pos = { x: 50, y: 50};
const relative_pos = {};
const canvas = document.querySelector('canvas');

canvas.addEventListener('mousemove', e => {
  relative_pos.x = e.offsetX;
  relative_pos.y = e.offsetY;
}, {once: true});

canvas.dispatchEvent(new MouseEvent('mousemove', {
  clientX: init_pos.x,
  clientY: init_pos.y
}));
// synchronously log
console.log(relative_pos);
canvas { 
  display: block;
  background: yellow;
  transform: scale(0.75);
}
#c4 {
  position: absolute;
  top: 0;
  background: cyan;
  transform: translateX(-250px) rotate(55deg);
  display: inline-block;
}
#c5 {
  background: magenta;
  transform: translate(50px);
  display: inline-block;
}
#c6 {
  background: pink;
  transform: rotate(45deg);
  display: inline-block;
}
<div id="c4">
  <div id="c5">
    <div id="c6">
      <canvas></canvas>
    </div>
  </div>
</div>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...