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

时间:2019-04-07 23:02:54

标签: javascript svg matrix

我在https://meyerweb.com/eric/css/colors/hsl-dist.html

处有HSL空间中CSS4颜色关键字分布的SVG可视化。

我最近添加了通过鼠标滚轮缩放和通过鼠标敲击和拖动进行平移的功能。由于可以在网上找到示例代码,因此可以使用matrixTransform.getScreenCTM().inverse()将点从屏幕空间转换为SVG坐标空间,但是如何在转换过程中转换鼠标的移动拖?现在,我只是将viewBox的坐标从event的X和Y值移开,这意味着图像的拖动速度比放大时的鼠标移动快。

作为一个例子,假设我放大了图像并拖动以平移,并且鼠标向左或向下拉动了一下。 event.movementX返回-37,而event.movementY返回6。如何确定SVG坐标等于多少,以便viewBox坐标正确移动?

注意::我知道有一些用于此类操作的库,但是我有意编写香草JS代码以了解有关SVG和JS的更多信息。因此,请不要发布“大声笑,只是使用库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);
}

如果需要更多,请在链接的页面上查看源代码;所有JS都在页面顶部。除了包含可视化的所有HSL值和颜色名称的文件之外,没有其他任何东西。

3 个答案:

答案 0 :(得分:6)

我的建议: 不必担心事件的movementX / Y属性。 只需担心鼠标在哪里开始以及现在在哪里。

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

对于鼠标的起始位置,可以在mousedown事件中对其进行测量。 使用您所使用的方法将其转换为SVG坐标中的位置, .getScreenCTM().inverse().matrixTransform()。 进行此转换后,您无需担心此点在屏幕上的什么位置。您只关心它在图片中的位置。这就是图片中您总是要移动到鼠标下方的要点。

在mousemove事件上,您使用相同的转换方法来找出鼠标当前在当前SVG坐标系内的位置。然后,您可以算出距离鼠标下方想要的点(同样,在SVG坐标中)有多远。那就是您用来变换图形的数量。我已按照您的示例进行操作,并且通过移动x的{​​{1}}和y部分来进行转换:

viewBox

您还可以在SVG中的组(function move(e) { var targetPoint = svgCoords(event, svg); shiftViewBox(anchorPoint.x - targetPoint.x, anchorPoint.y - targetPoint.y); } 元素)上使用transform来移动图形;只需确保对<g> / getScreenCTM()事件坐标进行转换的clientX调用使用相同的组元素即可。

完整演示,以进行拖动。我已经跳过了所有绘图代码和缩放效果。 但是缩放应该仍然有效,因为您要保存在全局值中的唯一位置已经转换为SVG坐标。

Y
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;
}

答案 1 :(得分: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对象的缩放比例及其在屏幕上的实际宽度来确定其缩放比例。 自然地,最初看起来是明智的。这种方法行不通,即使它看起来纯粹是偶然的。

但是事实证明,解决方案实质上是在接近鼠标移动时使用与缩放时使用的相同类型的代码。 .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);
}

很抱歉,答案很长,但是希望它能从头开始就足够说明它,以便其他任何想找到答案的人都可以得到完整的图片,如果他们不如您在此脚本中看到的那样。

答案 2 :(得分: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
}