2017-01-06 2 views
0

Je passais donc un peu de temps à jouer avec des éléments SVG purs (pas de bibliothèques externes) en les faisant glisser.OUI/NON - existe-t-il un moyen d'améliorer le déplacement de la souris avec des outils SVG purs?

En général tous les travaux, mais il y a cette question vicieuse pour la souris en mouvement rapide: - lorsque l'utilisateur mousedowns un élément SVG draggable près de son bord - puis fait glisser (mousemove) tels draggable trop vite - la souris « perd «le draggable

ici, le problème est décrit plus en détail: http://www.svgopen.org/2005/papers/AdvancedMouseEventModelForSVG-1/index.html#S3.2 ici aussi l'auteur a essayé de corriger UX en tirant parti de l'événement mouseout: http://nuclearprojects.com/blog/svg-click-and-drag-object-with-mouse-code/

Je copié l'extrait de code ci-dessus: http://codepen.io/cmer41k/pen/zNGwpa

La question que j'est:

Yat-il pas d'autre moyen (fourni par SVG pur) pour empêcher une telle « perte » d'un élément SVG tandis que la souris se déplace trop vite?

Ma tentative pour résoudre ceci était: - détecter (d'une manière ou d'une autre) que l'événement mouseout est arrivé sans finir le glissement. - et si oui (nous avons en quelque sorte détecté "déconnecter") - reconnecter l'élément SVG avec la position actuelle de la souris.

Y at-il une raison pour laquelle cela ne fonctionnerait pas?

code:

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"); 
 
    }

+0

'evt.clientY - clickY' ceux-ci ne sont pas javascript juridique ... –

Répondre

2

La partie la plus importante de votre code manque, à savoir comment ou plus précisément sur quel élément vous enregistrez les événements. Ce que vous faites essentiellement à empêchez ce problème d'enregistrer les événements mousemove et mouseup sur l'élément svg le plus externe, et non sur l'élément que vous voulez faire glisser. Lors du démarrage du glisser, enregistrez les événements sur l'élément svg et, une fois terminé, annulez leur enregistrement.

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

Vous devez stocker l'élément que vous êtes en train de faire glisser, il est donc disponible dans les autres gestionnaires d'événements. Ce que je fais en plus est de mettre "pointer-events" à "none" sur l'élément déplacé de sorte que vous puissiez réagir aux événements de la souris sous l'élément déplacé (c'est-à-dire trouver la cible ...)

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

mais ne pas oublier de le mettre à quelque chose raisonnable lors du déplacement se fait

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>

plus avancé

il y a encore deux les choses ne vont pas si bien avec ce code.

  1. il ne fonctionne pas pour les SVG viewBoxed ni pour les éléments parents transformés.
  2. tous les globals sont de mauvaises pratiques de codage.

Voici comment les corriger: Nr. 1 est résolu en convertissant les coordonnées de la souris en coordonnées locales en utilisant l'inverse de getScreenCTM (CTM = Current Transformation Matrix).

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()) 
    } 

Pour nr. 2 voir cette mise en œuvre:

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>

+0

Hey points négatifs merci pour la réponse. Pouvez-vous m'aider à comprendre (développez votre réponse un peu) pourquoi il est important d'assigner et d'enlever "dynamiquement" les auditeurs? cela résoudra-t-il les problèmes de "déconnexion" du mouvement de la souris? –

+0

étant donné que le gestionnaire d'événements se trouve sur l'élément svg, les événements de déplacement seraient déclenchés tout le temps. Dans un gestionnaire statique, vous pouvez simplement placer une instruction if autour du code de glissement, mais il est généralement plus élégant de ne pas l'utiliser si ce n'est pas nécessaire. il est plus convivial de cette façon, et il sépare le code de la présentation. Vous pouvez utiliser ce code sur n'importe quel SVG sans toucher au code svg ... Ce qui résout le problème de "déconnexion" "sticky" est que le gestionnaire est enregistré sur l'élément svg le plus externe, et non sur l'élément que vous déplacez. J'ai mis en évidence ces parties dans la réponse ... –

+0

BTW votre extrait de code fonctionne vraiment bien. Donc je suppose que cette chose d'écouteurs dynamiques + d'autres éléments permettent d'obtenir un comportement de souris très correct avec SVG! –