【问题标题】:Move to Mouse Click in SVG path (d3.js)在 SVG 路径中移动到鼠标单击 (d3.js)
【发布时间】:2019-08-25 07:07:26
【问题描述】:

我需要在 mousedown 事件中创建一个圆圈并将其移动到 SVG 路径中的最近点

here is the jsfiddle

代码:

        var points = [[180,300],[234,335],[288,310],[350,290],[405,300],[430,305],[475,310],[513,300],[550,280]];

        var width = 1000, height = 600;
        var line = d3.svg.line().interpolate("cardinal");
        var svg = d3.select("#Con").append("svg").attr("width", width).attr("height", height);
        var path = svg.append("path").datum(points).attr("d", line);
        var line = svg.append("line");
        var circle = svg.append("   circle").attr("cx", -10).attr("cy", -10).attr("r", 3.5);
        svg.append("rect").attr("width", width).attr("height", height).on("mousedown", mouseclick);
        function mouseclick() {
            var m = d3.mouse(this),p = closestPoint(path.node(), m);
            circle.attr("cx", p[0]).attr("cy", p[1]);
        }
        function closestPoint(pathNode, point) {
            var pathLength = pathNode.getTotalLength(),precision = 8,best,bestLength,bestDistance = Infinity;
            for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
                if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
                    best = scan, bestLength = scanLength, bestDistance = scanDistance;
                }
            }
            precision /= 2;
            while (precision > 0.5) {
                var before,after,beforeLength,afterLength,beforeDistance,afterDistance;
                if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
                    best = before, bestLength = beforeLength, bestDistance = beforeDistance;
                } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
                    best = after, bestLength = afterLength, bestDistance = afterDistance;
                } else {
                    precision /= 2;
                }
            }
            best = [best.x, best.y];
            best.distance = Math.sqrt(bestDistance);
            return best;
            function distance2(p) {
                var dx = p.x - point[0],dy = p.y - point[1];
                return dx * dx + dy * dy;
            }
        }

当我在 SVG 空间中单击时,我需要将圆圈移动到路径中的最近点

在我的代码中,圆圈在没有动画的情况下移动,我需要对其进行动画处理,以便它在路径上从一个点移动到另一个点

无论是大距离还是小距离,我都需要它始终以一种速度移动

像这样:

https://a.top4top.net/p_11885szd41.gif

【问题讨论】:

  • 这似乎与stackoverflow.com/q/54523405/1978785 的重复很接近 这个问题是否涵盖了您的问题?如果不是,您可以将您的问题编辑得更具体吗?
  • 从你的 jsfiddle 看来你没有遇到鼠标按下事件的问题。您需要帮助的只是动画吗?这个中风动画问题似乎也非常接近您的要求。这个问题有帮助吗:stackoverflow.com/questions/53738351/…
  • 在这两个问题中路径都在改变(动画),我不想改变路径,我想要动画圆圈运动。
  • 感谢您的解释。你现在有几个很好的答案。对所有有帮助的答案进行投票,并最终接受您认为最能回答您的问题的答案。如果没有一个答案足以回答您的问题,您可以编辑您的问题以更清楚地说明您在问什么。

标签: javascript html d3.js svg


【解决方案1】:

我没有使用你的代码,但我希望你能明白。

我没有使用圆圈,而是使用轨道:

<use id="theUse" xlink:href="#track"

这条轨道有一个stroke-dasharray".1 398.80" 这意味着一条.1(非常非常小)和一条398.80 的间隙与轨道一样长。 stroke-width 是 7 和 stroke-linecap= "round",这会将破折号变成一个圆圈。我正在使用stroke-dashoffset 更改破折号(圆圈)的位置,并且为了动画化我在css 中使用transition: stroke-dashoffset 1s; 的更改。

希望对你有帮助。

let m;
let L = track.getTotalLength();
let _start = {x:180,y:30}
let _end = {x:550,y:280}
let l = dist(_start, _end);
theUse.setAttributeNS(null,"stroke-dashoffset",L);

svg.addEventListener("click",(e)=>{
  m = oMousePosSVG(e)
  
  let pos = m.x - _start.x;
  let theDistance = map(pos,_start.x,_end.x,0,L)
  let s_dof = constrain(L-theDistance, .5, L-.5)
  
  theUse.setAttributeNS(null,"stroke-dashoffset",s_dof)  
})

function oMousePosSVG(e) {
      var p = svg.createSVGPoint();
      p.x = e.clientX;
      p.y = e.clientY;
      var ctm = svg.getScreenCTM().inverse();
      var p =  p.matrixTransform(ctm);
      return p;
}

function dist(p1, p2) {
  let dx = p2.x - p1.x;
  let dy = p2.y - p1.y;
  return Math.sqrt(dx * dx + dy * dy);
}

function map(n, a, b, _a, _b) {
  let d = b - a;
  let _d = _b - _a;
  let u = _d / d;
  return _a + n * u;
}

function constrain(n, low, high) {
  return Math.max(Math.min(n, high), low);
};
svg {
  border: 1px solid;
}
path {
  fill: none;
}

#theUse {
 
  transition: stroke-dashoffset 1s;
  
}
<svg id="svg" viewBox="150 250 450 100">
  <defs>
<path id="track" d="M180,300Q223.2,334,234,335C250.2,336.5,270.6,316.75,288,310S332.45,291.5,350,290S393,297.75,405,300S419.5,303.5,430,305S462.55,310.75,475,310S501.75,304.5,513,300Q520.5,297,550,280"></path>
  </defs> 
  <use  xlink:href="#track" stroke="black" />
  <use id="theUse" xlink:href="#track" stroke-width="7" stroke-dasharray = ".1 398.80" stroke="red" stroke-linecap= "round" />
  
 
</svg>

【讨论】:

  • 感谢这个有用的答案,唯一的问题是过渡是当我点击非常接近圆圈时它移动缓慢,当我点击远离它时它移动很快
【解决方案2】:

此答案会修改您的代码以从行首或从最后一次鼠标单击到当前鼠标单击移动一个圆圈。它通过使用 setTimeout 调用 animate 方法来为圆的移动设置动画,直到圆从行首移动到鼠标单击的位置。

有趣的代码在这里:

        // getAnimate returns a function that is within a closure
       function getAnimate(pLength, path, currentIndex, finishPos, forward){
              let animate = function (){
                let scan = path.node().getPointAtLength(currentIndex);
                if (scan.x < finishPos || !forward && scan.x > finishPos){
                  circle.attr("cx", scan.x).attr("cy", scan.y);
                }
              if (forward){
                currentIndex += 1;
                lastIndex = currentIndex;
                if (scan.x < finishPos){
                  setTimeout(animate, 50);
                  }
                } else {
                    currentIndex -= 1;
                    lastIndex = currentIndex;
                if (scan.x > finishPos){
                  setTimeout(animate, 50);
                }
              }
            }
             return animate;
           }

    var points = [[80,100],[134,115],[188,130],[250,120],[305,120],[330,101],[375,103],[413,100],[550,90]];
                var width = 500, height = 200;
                var line = d3.svg.line().interpolate("cardinal");
                var svg = d3.select("#Con").append("svg").attr("width", width).attr("height", height);
                var path = svg.append("path").datum(points).attr("d", line);
                var line = svg.append("line");
                var circle = svg.append("circle").attr("cx", -10).attr("cy", -10).attr("r", 3.5);
                svg.append("rect").attr("width", width).attr("height", height).on("mousedown", mouseclick);
                var lastIndex = 0;
                function mouseclick() {
                  let m = d3.mouse(this);
                  let p = closestPoint(path.node(), m);
                  let forward = true;
                  let currentPoint = path.node().getPointAtLength(lastIndex);                  
                   if (p[0] < currentPoint.x){
                     forward = false;
                   }
                  let pathLength = path.node().getTotalLength();
                  getAnimate(pathLength, path, lastIndex, p[0], forward)();
                }

                function getAnimate(pLength, path, currentIndex, finishPos, forward){
                  let animate = function (){
                    let scan = path.node().getPointAtLength(currentIndex);
                    if (scan.x < finishPos || !forward && scan.x > finishPos){
                      circle.attr("cx", scan.x).attr("cy", scan.y);
                    }
                  if (forward){
                  currentIndex += 1;
                  lastIndex = currentIndex;
                  if (scan.x < finishPos){
                    setTimeout(animate, 50);
                    }
                    } else {
                          currentIndex -= 1;
                          lastIndex = currentIndex;
                  if (scan.x > finishPos){
                    setTimeout(animate, 50);
                    }
                    }
                }
                 return animate;
               }

                function closestPoint(pathNode, point) {
                    var pathLength = pathNode.getTotalLength(),precision = 8,best,bestLength,bestDistance = Infinity;
                    for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) {
                        if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) {
                            best = scan, bestLength = scanLength, bestDistance = scanDistance;
                        }
                    }
                    precision /= 2;
                    while (precision > 0.5) {
                        var before,after,beforeLength,afterLength,beforeDistance,afterDistance;
                        if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) {
                            best = before, bestLength = beforeLength, bestDistance = beforeDistance;
                        } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) {
                            best = after, bestLength = afterLength, bestDistance = afterDistance;
                        } else {
                            precision /= 2;
                        }
                    }
                    best = [best.x, best.y];
                    best.distance = Math.sqrt(bestDistance);
                    return best;
                    function distance2(p) {
                        var dx = p.x - point[0],dy = p.y - point[1];
                        return dx * dx + dy * dy;
                    }
                }
    * {
        margin: 0;
        padding: 0;
    }
    #Con {
        border: 1px solid black;
        margin: auto;
        width: 1000px;
        height: 600px;
    }
    #Map{
        z-index: -1000;
        position: absolute;
    }
    #Cha {
        position: absolute;
    }
    path {
        z-index: 1000;
        fill: none;
        stroke: #000;
        stroke-width: 1.5px;
    }
    line {
        fill: none;
        stroke: red;
        stroke-width: 1.5px;
    }
    circle {
      fill: red;
    }
    rect {
        fill: none;
        pointer-events: all;
    }
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.5.0"></script>

     <div id="Con"></div>

【讨论】:

  • 感谢您回答我的问题,这与我想要的非常接近,但我需要圆圈从当前位置移动(而不是从路径的开头)。
  • 我进行了更新,以便代码跟踪最后一次鼠标点击并确定方向,以便动画可以将红色圆圈从上一次鼠标点击移动到当前鼠标点击。
  • 如果您还有其他问题,请将其作为新问题发布。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-26
  • 1970-01-01
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
相关资源
最近更新 更多