如何将鼠标移动距离转换为SVG坐标空间?

4
我有一个CSS4颜色关键字在HSL空间中分布的SVG可视化,链接在这里:https://meyerweb.com/eric/css/colors/hsl-dist.html 我最近添加了通过鼠标滚轮进行缩放和通过鼠标点击并拖动进行平移的功能。我能够使用matrixTransform.getScreenCTM().inverse()将屏幕空间中的点转换为SVG坐标空间,这得益于我在网上找到的示例代码,但我该如何转换拖动期间的鼠标移动?目前,我只是通过从event获取的X和Y值来移动viewBox坐标,这意味着当缩放时,图像拖动速度比鼠标移动速度快。
例如,假设我将图像缩放并拖动进行平移,并且我向左抛出鼠标并稍微向下。 event.movementX返回-37,而event.movementY返回6。 我该如何确定这相当于SVG坐标中的多远,以便正确移动viewBox坐标?
(注意:我知道有这种情况的库,但我有意使用原始JS代码,以便更多地了解SVG和JS。所以,请不要发布“lol just use library X”然后就此结束。谢谢!)
编辑添加:被要求发布代码。发布整个JS似乎过长,但这是在mousemove事件上触发的函数:
function dragger(event) {
    var target = document.getElementById('color-wheel');
    var coords = parseViewBox(target);
    coords.x -= event.movementX;
    coords.y -= event.movementY;
    changeViewBox(target,coords);
}

如果需要更多信息,请查看链接页面的源代码;所有JavaScript都在页面顶部。除了一个仅包含可视化所需的HSL值和颜色名称的文件外,没有外部文件。

嗨,Eric。这是你可以直接添加到帖子中的代码量吗? - jhpratt
我不确定其中有多少是相关的,但我会添加我认为需要修改的函数。 - Eric A. Meyer
3个回答

9

我的建议是:不用担心事件中的/属性,只需要关注鼠标开始和现在所在的位置。

(这样做还有一个额外的好处,即使您错过了一些事件也可以得到相同的结果:可能是因为鼠标移出了窗口,或者是因为您想将事件分组,每帧只运行一次代码。)

对于鼠标开始的位置,您可以在mousedown事件中测量。使用您正在使用的方法将其转换为SVG坐标系中的位置,例如 .getScreenCTM().inverse().matrixTransform()。完成此转换后,您不再关心该点在屏幕上的位置。您只关心它在图片中的位置。这是图片中始终要移动以位于鼠标下方的点。

在mousemove事件中,您可以使用同样的转换方法来找出鼠标当前在当前SVG坐标系中的位置。然后,您可以计算出它距离您想放置在鼠标下方的点(同样在SVG坐标系内)有多远。这就是您用于转换图形的量。我遵循了您的示例,并通过移动viewBox的和部分来进行转换:

function move(e) {
  var targetPoint = svgCoords(event, svg);
  shiftViewBox(anchorPoint.x - targetPoint.x,
               anchorPoint.y - targetPoint.y);
}

您还可以通过对SVG中的组元素(<g>元素)进行变换(transform)来移动图形;只需确保在进行从clientX/Y事件坐标转换的getScreenCTM()调用时使用同一组元素即可。
以下是拖动平移的完整示例。我已跳过所有绘图代码和缩放效果。但缩放仍应正常工作,因为您在全局值中保存的唯一位置已经转换为SVG坐标。

var svg = document.querySelector("svg");
var anchorPoint;

function shiftViewBox(deltaX, deltaY) {
 svg.viewBox.baseVal.x += deltaX;
 svg.viewBox.baseVal.y += deltaY;
}

function svgCoords(event,elem) {
 var ctm = elem.getScreenCTM();
 var pt = svg.createSVGPoint();
    // Note: rest of method could work with another element,
    // if you don't want to listen to drags on the entire svg.
    // But createSVGPoint only exists on <svg> elements.
 pt.x = event.clientX;
 pt.y = event.clientY;
 return pt.matrixTransform(ctm.inverse());
}

svg.addEventListener("mousedown", function(e) {
  anchorPoint = svgCoords(event, svg);
  window.addEventListener("mousemove", move);
  window.addEventListener("mouseup", cancelMove);
});

function cancelMove(e) {
  window.removeEventListener("mousemove", move);
  window.removeEventListener("mouseup", cancelMove);
  anchorPoint = undefined;
}

function move(e) {
  var targetPoint = svgCoords(event, svg);
  shiftViewBox(anchorPoint.x - targetPoint.x,
               anchorPoint.y - targetPoint.y);
}
body {
  display: grid;
  margin: 0;
  min-height: 100vh;
}

svg {
  margin: auto;
  width: 70vmin;
  height: 70vmin;
  border: thin solid gray;
  cursor: move;
}
<svg viewBox="-40 -40 80 80">
  <polygon fill="skyBlue"
           points="0 -40, 40 0, 0 40 -40 0" />
</svg>


2
因此,脚本需要一些东西来协调SVG移动的矢量与屏幕上鼠标移动的矢量。尽管事件发生在您的目标SVG上,但MouseEvent属性仅涉及您的屏幕。
“MouseEvent接口的movementX只读属性提供了给定事件和前一个mousemove事件之间鼠标指针X坐标的差异。换句话说,属性的值计算如下:currentEvent.movementX = currentEvent.screenX - previousEvent.screenX。”
来自https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/movementX “MouseEvent接口的screenX只读属性提供了全局(屏幕)坐标中鼠标指针的水平坐标(偏移量)。”
所以你正在测量的,据我所知唯一可以直接测量而不需要额外库或复杂性的是指针在屏幕上的像素术语中的移动。使这个工作以SVG运动的向量为基础的唯一方法是将屏幕上的移动转换为与缩放的SVG相关的维度。
我的最初想法是,您可以通过使用SVG对象的viewbox和在屏幕上的实际宽度的某种组合来计算其缩放比例。自然而然的,最初看起来合理的方法并不可行。如果它似乎可以,那么纯粹是偶然的。
但事实证明,解决方案基本上是使用与您处理鼠标移动时所使用的相同类型的代码。 .getScreenCTM().inverse()函数正是您需要的。但是,您不需要试图找到SVG上的单个点来工作,而是需要通过比较SVG上的两个点来找出屏幕上的距离在SVG中的翻译。
我在这里提供的不一定是最优解,但希望能够解释并为您提供进一步工作的东西...
function dragger(event) {
    var target = document.getElementById('color-wheel');
    var coords = parseViewBox(target);


    //Get an initial point in the SVG to start measuring from
    var start_pt = target.createSVGPoint();

    start_pt.x = 0;
    start_pt.y = 0;

    var svgcoord = start_pt.matrixTransform(target.getScreenCTM().inverse());


    //Create a point within the same SVG that is equivalent to 
    //the px movement by the pointer
    var comparison_pt = target.createSVGPoint();

    comparison_pt.x = event.movementX;
    comparison_pt.y = event.movementY;

    var svgcoord_plus_movement = comparison_pt.matrixTransform(target.getScreenCTM().inverse());


    //Use the two SVG points created from screen position values to determine 
    //the in-SVG distance to change coordinates
    coords.x -=  (svgcoord_plus_movement.x - svgcoord.x);


    //Repeat the above, but for the Y axis
    coords.y -= (svgcoord_plus_movement.y - svgcoord.y);


    //Deliver the changes to the SVG to update the view
    changeViewBox(target,coords);
}

抱歉回答有点冗长,但希望能从一开始就解释清楚,这样如果其他人在寻找答案时还没有像你在这个脚本中一样深入研究,他们也可以得到整个情况的完整图片。

0
MouseEvent 中,我们可以得到 clientXmovememntX。结合起来,我们可以推断出我们的最后位置。然后,我们可以取当前位置的变换并将其减去上一个位置的变换:
element.onpointermove = e => {
    const { clientX, clientY, movementX, movementY } = e;
    const DOM_pt = svg.createSVGPoint();
    DOM_pt.x = clientX;
    DOM_pt.y = clientY;
    const { x, y } = DOM_pt.matrixTransform(svgs[i].getScreenCTM().inverse());
    DOM_pt.x += movementX;
    DOM_pt.y += movementY;
    const { x: last_x, y: last_y } = DOM_pt.matrixTransform(svgs[i].getScreenCTM().inverse());
    const dx = last_x - x;
    const dy = last_y - y;
    // TODO: use dx & dy
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接