获取已转换元素的局部坐标中的触摸位置

Getting the touched position in local coordinates of a transformed element

我有一个元素已经用 matrix3d 进行了转换以赋予它视角。它代表手持设备的屏幕。

有一张显示手持设备本身的背景图片,未进行转换。放置它的元素被定位,屏幕元素绝对定位在其中,在 left: 0; top: 0;,然后转换,原点在容器的左上角。这是我将其与背景图像完美对齐的最简单方法(我使用 this very handy tool 得出矩阵),并将屏幕元素从角落移开。

我希望能够通过鼠标和触摸事件与屏幕进行交互。为此,在单击或触摸事件时,我需要在屏幕元素的局部坐标系中找到坐标——即发生变换之前的坐标。换句话说,当单击手持设备屏幕的左上角( 而不是 页面上其边界框的左上角!)时,我想要 [0, 0],当单击屏幕的右上角时,在这个转换的情况下,它实际上在页面的上方和右侧,我想要 [untransformedWidth, 0].

鼠标事件提供 offsetXoffsetY 据称正是这样做的(更多内容见下文),但触摸事件没有这些属性,所以我需要一种方法来计算它们我自己。

使用 math.js I can feed in the transformation matrix and invert it. I have some code to loop over the CSS rules 获取 transform: matrix3d(...) 规则(如果不需要,我不想在我的代码中重复它),我将跳过 - 我知道它有效,因为数字匹配 CSS.

请注意 CSS 的矩阵元素按列顺序排列,因此 matrix3d(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) 看起来像这样的常规矩阵表示法:

┌            ┐
│ a  e  i  m │
│ b  f  j  n │
│ c  g  k  o │
│ d  h  l  p │
└            ┘

与此同时,math.js 希望逐行声明其矩阵,如 [[a, e, i, m], [b, f, j, n]...

因此,从 matrix3d(...) 表达式内部的数字元素列表开始,按 CSS 顺序,我正在构建和反转矩阵,如下所示:

var rows = [[], [], [], []];
for (var i = 0; i < elements.length; i++) {
  rows[i % 4][Math.floor(i / 4)] = elements[i];
}

var matrixTransform = math.matrix(rows);

var invertedMatrixTransform = math.inv(matrixTransform);

然后我在屏幕元素上设置鼠标事件处理程序:

screen.addEventListener('mousedown', function (event) {
  var rect = container.getBoundingClientRect();
  var x = event.clientX - rect.left;
  var y = event.clientY - rect.top;

如果我将标记(相对于容器)移动到此点 [x, y],它就会出现在我单击的位置。所以我知道这很有效。然后我将这些坐标的向量乘以逆变换矩阵:

  var vector = math.matrix([x, y, 0, 1]);
  var result = math.multiply(inverseMatrixTransform, vector);

如果我将另一个标记(这个标记相对于屏幕元素)移动到生成的矢量值 [result.get([0]), result.get([1])],它会移动到与前一个标记大致相同的位置,但不太对。似乎离原点越远,误差越大,直到右边缘和底部边缘真的很糟糕。

但是如果我检查 offsetXoffsetY 会怎样?好吧,事实证明答案取决于浏览器。

在 Firefox 中,用 offset* 找到的坐标也不匹配点击的位置。它们与我计算的不完全相同,但仅相差几个像素。它们与我的计算值一样远离真正的点击点。

但是在Chrome中用offset*找到的坐标是完美的。

这是一个 jsfiddle

我的计算有什么问题吗?有没有办法让我模仿 Chrome 的结果,但没有 offset* 属性?

我不完全确定这个理论,我在处理类似问题时读到过这个,但这是我发现的。矩阵的乘法(3d 变换的矩阵 - 齐次坐标和光标的位置)除了 x 和 y 之外还产生两个值。第一个是 z,另一个是 w,用于将 3d 对象投影到 2d 平面上。

如果用向量的 x 和 y 值除以 w 坐标,就会得到笛卡尔平面上光标的正确 position/mapping。

所以,我会用这些替换您 fiddle 中第 69-70 行的代码:

var x = result.get([0]);
var y = result.get([1]);
var w = result.get([3]);
screenCrosshair.style.left = x / w + 'px';
screenCrosshair.style.top = y / w + 'px';

这里是更新fiddle: https://jsfiddle.net/x3ruc3tL/1/

然后您不需要浏览器的 offsetXoffsetY 值来找到正确的位置。

请阅读以下文章了解更多信息:

https://en.wikipedia.org/wiki/3D_projection#Perspective_projection http://deltaorange.com/2012/03/08/the-truth-behind-homogenous-coordinates/