是/否 - 有没有办法用纯SVG工具改善鼠标拖动?

时间:2017-01-06 21:54:26

标签: svg mouse drag

所以我花了一些时间玩纯粹的(没有外部库)SVG元素拖动。

一般来说所有的作品都有,但快速移动的鼠标存在这个令人讨厌的问题: - 当用户将可拖动的SVG元素靠近其边缘时 - 然后拖动(mousemove)这样拖得太快了 - 鼠标“失去”可拖动的

此处更详细地描述了该问题: http://www.svgopen.org/2005/papers/AdvancedMouseEventModelForSVG-1/index.html#S3.2 此外,作者还尝试通过利用mouseout事件来修复UX: http://nuclearprojects.com/blog/svg-click-and-drag-object-with-mouse-code/

我在此处复制了上述代码段:http://codepen.io/cmer41k/pen/zNGwpa

我的问题是:

当鼠标移动太快时,是否没有其他方法(由纯SVG提供)来防止SVG元素的这种“丢失”?

我解决这个问题的尝试是: - 检测(以某种方式)鼠标停止事件发生而不完成拖动。 - 如果是这样(我们检测到“断开连接”) - 用当前鼠标位置重新连接SVG元素。

有什么理由不起作用吗?

代码:

    var click=false; // flag to indicate when shape has been clicked
    var clickX, clickY; // stores cursor location upon first click
    var moveX=0, moveY=0; // keeps track of overall transformation
    var lastMoveX=0, lastMoveY=0; // stores previous transformation (move)
    function mouseDown(evt){
        evt.preventDefault(); // Needed for Firefox to allow dragging correctly
        click=true;
        clickX = evt.clientX; 
        clickY = evt.clientY;
        evt.target.setAttribute("fill","green");
    }

    function move(evt){
        evt.preventDefault();
        if(click){
            moveX = lastMoveX + ( evt.clientX – clickX );
            moveY = lastMoveY + ( evt.clientY – clickY );

            evt.target.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
        }
    }

    function endMove(evt){
        click=false;
        lastMoveX = moveX;
        lastMoveY = moveY;
        evt.target.setAttribute("fill","gray");
    }

1 个答案:

答案 0 :(得分:2)

缺少代码中最重要的部分,即如何或更具体地说明您注册事件的元素。

你基本上做什么防止这个问题是在最外面的svg元素上注册mousemove和mouseup事件,而不是在你想要拖动的元素上注册

svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)

开始拖动时,在svg元素上注册事件,完成后取消注册它们。

svg.removeEventListener("mousemove", move)
svg.removeListener("mouseup", endMove)

您必须存储当前正在拖动的元素,因此它可以在其他事件处理程序中使用。

我另外做的是在拖动时将指针事件设置为“无” 元素,以便您可以对拖动元素下面的鼠标事件做出反应(例如,找到放置目标...)

evt.target.setAttribute("pointer-events", "none")

但是不要忘记在拖动完成后将其设置为合理的

evt.target.setAttribute("pointer-events", "all")

var click = false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX = 0,
  moveY = 0; // keeps track of overall transformation
var lastMoveX = 0,
  lastMoveY = 0; // stores previous transformation (move)
var currentTarget = null

function mouseDown(evt) {
  evt.preventDefault(); // Needed for Firefox to allow dragging correctly
  click = true;
  clickX = evt.clientX;
  clickY = evt.clientY;
  evt.target.setAttribute("fill", "green");
  // register move events on outermost SVG Element
  currentTarget = evt.target
  svg.addEventListener("mousemove", move)
  svg.addEventListener("mouseup", endMove)
  evt.target.setAttribute("pointer-events", "none")
}

function move(evt) {
  evt.preventDefault();
  if (click) {
    moveX = lastMoveX + (evt.clientX - clickX);
    moveY = lastMoveY + (evt.clientY - clickY);
    currentTarget.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
  }
}

function endMove(evt) {
  click = false;
  lastMoveX = moveX;
  lastMoveY = moveY;
  currentTarget.setAttribute("fill", "gray");
  svg.removeEventListener("mousemove", move)
  svg.removeEventListener("mouseup", endMove)
  currentTarget.setAttribute("pointer-events", "all")
}
<svg id="svg" width="800" height="600" style="border: 1px solid black; background: #E0FFFF;">
  <rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
  <circle id="mycirc" cx="60" cy="60" r="22" onmousedown="mouseDown(evt)" />
</svg>

更高级

这段代码还有两件事情不太好。

  1. 它不适用于viewBoxed SVG,也不适用于内部元素 转变为父母。
  2. 所有的全局变量都是糟糕的编码习惯。
  3. 以下是如何解决这些问题: NR。通过使用getScreenCTM(CTM =当前转换矩阵)的反转将鼠标坐标转换为局部坐标来解决1。

    function globalToLocalCoords(x, y) {
        var p = elem.ownerSVGElement.createSVGPoint()
        var m = elem.parentNode.getScreenCTM()
        p.x = x
        p.y = y
        return p.matrixTransform(m.inverse())
      }
    

    对于nr。 2看到这个实现:

    var dre = document.querySelectorAll(".draggable")
    for (var i = 0; i < dre.length; i++) {
      var o = new Draggable(dre[i])
    }
    
    function Draggable(elem) {
      this.target = elem
      this.clickPoint = this.target.ownerSVGElement.createSVGPoint()
      this.lastMove = this.target.ownerSVGElement.createSVGPoint()
      this.currentMove = this.target.ownerSVGElement.createSVGPoint()
      this.target.addEventListener("mousedown", this)
      this.handleEvent = function(evt) {
        evt.preventDefault()
        this.clickPoint = globalToLocalCoords(evt.clientX, evt.clientY)
        this.target.classList.add("dragged")
        this.target.setAttribute("pointer-events", "none")
        this.target.ownerSVGElement.addEventListener("mousemove", this.move)
        this.target.ownerSVGElement.addEventListener("mouseup", this.endMove)
      }
      this.move = function(evt) {
        var p = globalToLocalCoords(evt.clientX, evt.clientY)
        this.currentMove.x = this.lastMove.x + (p.x - this.clickPoint.x)
        this.currentMove.y = this.lastMove.y + (p.y - this.clickPoint.y)
        this.target.setAttribute("transform", "translate(" + this.currentMove.x + "," + this.currentMove.y + ")")
      }.bind(this)
    
      this.endMove = function(evt) {
        this.lastMove.x = this.currentMove.x
        this.lastMove.y = this.currentMove.y
        this.target.classList.remove("dragged")
        this.target.setAttribute("pointer-events", "all")
        this.target.ownerSVGElement.removeEventListener("mousemove", this.move)
        this.target.ownerSVGElement.removeEventListener("mouseup", this.endMove)
      }.bind(this)
    
      function globalToLocalCoords(x, y) {
        var p = elem.ownerSVGElement.createSVGPoint()
        var m = elem.parentNode.getScreenCTM()
        p.x = x
        p.y = y
        return p.matrixTransform(m.inverse())
      }
    }
    .dragged {
      fill-opacity: 0.5;
      stroke-width: 0.5px;
      stroke: black;
      stroke-dasharray: 1 1;
    }
    .draggable{cursor:move}
    <svg id="svg" viewBox="0 0 800 600" style="border: 1px solid black; background: #E0FFFF;">
      <rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
      <circle class="draggable" id="mycirc" cx="60" cy="60" r="22" fill="blue" />
      <g transform="rotate(45,175,75)">
        <rect class="draggable" id="mycirc" x="160" y="60" width="30" height="30" fill="green" />
      </g>
      <g transform="translate(200 200) scale(2 2)">
        <g class="draggable">
          <circle cx="0" cy="0" r="30" fill="yellow"/>
          <text text-anchor="middle" x="0" y="0" fill="red">I'm draggable</text>
        </g>
      </g>
    </svg>
    <div id="out"></div>