Silkograph
Silkograph

Reputation: 85

JQuery draggable element controlling a SVG object jumping for the first time

I have to adjust a SVG's position and scale using DIV having handles. Everything is working as expected except SVG is jumping the first time at the time of dragging the div handles. Only the SVG is jumping.

See here that the class "selection" is a DIV controlling the selected SVG with attribute ("[selection=true]")

  $( ".selector" ).resizable({
  aspectRatio: false,
  handles: {
    'nw': '#nwgrip',
    'ne': '#negrip',
    'sw': '#swgrip',
    'se': '#segrip' },
resize: function(event, ui) {
    console.log(ui.size.width);
    $('#posW').text('Width: ' + Math.round(ui.size.width) );
    $('#posH').text('Height: ' + Math.round(ui.size.height) );

    
    $("[selection=true]").attr("width",  Math.round(ui.size.width) ); 
    $("[selection=true]").attr("height", Math.round(ui.size.height) );
    
     $("[selection=true]").attr("x",  Math.round(ui.position.left) ); 
     $("[selection=true]").attr("y", Math.round(ui.position.top) );
    
}

}).draggable({
drag: function(event, ui) {
    var offset = $(this).offset();
    var xPos = offset.left;
    var yPos = offset.top;
    $("[selection=true]").attr("x",   Math.round(xPos) ); 
    $("[selection=true]").attr("y",  Math.round(yPos) );
    $('#posX').text('x: ' + xPos);
    $('#posY').text('y: ' + yPos);
    
    } 
  }
);

Please check jsfiddle link

Is this a bug that can be fixed?

Upvotes: 1

Views: 229

Answers (1)

Paul LeBeau
Paul LeBeau

Reputation: 101820

Here's a working version of your original. It handles all clicking, dragging, and selector box behaviour within the "canvas" SVG.

It uses pure JS and manipulates the badge <svg> and selector box elements using DOM methods.

Hopefully it is fairly easily to follow what is going on.

let selectedBadge = null;
let isDraggingRect = null;
let isDraggingHandle = null;
let dragOffsetX = 0;
let dragOffsetY = 0;

// Event handlers for the badges
let badges = document.querySelectorAll(".canvas > .badge");
badges.forEach(b => b.addEventListener("click", select));

// Event handlers for selector box and handles
let selectorRect = document.querySelector("#selector rect");
selectorRect.addEventListener("mousedown", selectorMouseDown);
let handleNW = document.getElementById("nwgrip");
let handleNE = document.getElementById("negrip");
let handleSW = document.getElementById("swgrip");
let handleSE = document.getElementById("segrip");
let grips = document.querySelectorAll("#selector > circle");
grips.forEach(g => {
  g.addEventListener("mousedown", gripMouseDown);
});
// Attach mousemove and mouseup events to SVG for dragging purposes
// We attach to the parent SVG because mouse events on small elements
// will be missed if you move the mouse outside the element.
let canvasSVG = document.querySelector("#svg_obj > .canvas");
canvasSVG.addEventListener("mousemove", mouseMove);
canvasSVG.addEventListener("mouseup", mouseUp);


// select a badge
function select(evt) {
  hideSelector(selectedBadge);
  selectedBadge = evt.target.ownerSVGElement;
  showSelector(selectedBadge)
}


function showSelector(badge) {
  setSelectorDimensionsTo({x: badge.x.baseVal.value,
                           y: badge.y.baseVal.value,
                           width: badge.width.baseVal.value,
                           height: badge.height.baseVal.value});
  document.getElementById("selector").classList.add("show");
}

function hideSelector(badge) {
  if (selectedBadge) {
    document.getElementById("selector").classList.remove("show");
    selectedBadge = null;
  }
}

function setSelectorDimensionsTo(bounds) {
  selectorRect.x.baseVal.value = bounds.x;  
  selectorRect.y.baseVal.value = bounds.y;  
  selectorRect.width.baseVal.value = bounds.width;
  selectorRect.height.baseVal.value = bounds.height;  

  handleNW.cx.baseVal.value = bounds.x;  
  handleNW.cy.baseVal.value = bounds.y;  
  handleNE.cx.baseVal.value = bounds.x + bounds.width;
  handleNE.cy.baseVal.value = bounds.y;
  handleSW.cx.baseVal.value = bounds.x;
  handleSW.cy.baseVal.value = bounds.y + bounds.height;
  handleSE.cx.baseVal.value = bounds.x + bounds.width;
  handleSE.cy.baseVal.value = bounds.y + bounds.height;
}

function moveSelectorTo(x, y) {
  selectorRect.x.baseVal.value = x;  
  selectorRect.y.baseVal.value = y;  

  let w = selectorRect.width.baseVal.value;
  let h = selectorRect.height.baseVal.value;
  handleNW.cx.baseVal.value = x;  
  handleNW.cy.baseVal.value = y;  
  handleNE.cx.baseVal.value = x + w;
  handleNE.cy.baseVal.value = y;
  handleSW.cx.baseVal.value = x;
  handleSW.cy.baseVal.value = y + h;
  handleSE.cx.baseVal.value = x + w;
  handleSE.cy.baseVal.value = y + h;
}

function moveSelectedBadgeTo(x, y) {
  selectedBadge.x.baseVal.value = x;  
  selectedBadge.y.baseVal.value = y;  
}

function selectorMouseDown(evt) {
  isDraggingRect = selectedBadge;
  let mousePos = mouseCoordsToSVGCoords(evt.offsetX, evt.offsetY);
  dragOffsetX = mousePos.x - selectedBadge.x.baseVal.value;
  dragOffsetY = mousePos.y - selectedBadge.y.baseVal.value;
}

function mouseUp(evt) {
  isDraggingRect = null;
  isDraggingHandle = null;
}

// Handles both:
// - dragging selector rect
// - dragging selector grip/handle
function mouseMove(evt) {
  if (isDraggingRect)
  {
    // Move selector
    let mousePos = mouseCoordsToSVGCoords(evt.offsetX, evt.offsetY);
    moveSelectorTo(mousePos.x - dragOffsetX, mousePos.y - dragOffsetY);
    // Move badge
    moveSelectedBadgeTo(mousePos.x - dragOffsetX, mousePos.y - dragOffsetY);
  }
  else if (isDraggingHandle)
  {
    gripMouseMove(evt);
  }
}


// Convert page mouse coords to SVG coords.
// Takes into account any scaling due to the presence of a viewBox.
function mouseCoordsToSVGCoords(mouseX, mouseY) {
  var pt = canvasSVG.createSVGPoint();
  pt.x = mouseX;
  pt.y = mouseY;
  return pt.matrixTransform(canvasSVG.getScreenCTM().inverse());
}


function gripMouseDown(evt) {
  isDraggingHandle = evt.target;
  let mousePos = mouseCoordsToSVGCoords(evt.offsetX, evt.offsetY);
  dragOffsetX = mousePos.x - isDraggingHandle.cx.baseVal.value;
  dragOffsetY = mousePos.y - isDraggingHandle.cy.baseVal.value;
}

function gripMouseUp(evt) {
  isDraggingHandle = null;
}

function gripMouseMove(evt) {
  // Move handle thus resizing selector
  let mousePos = mouseCoordsToSVGCoords(evt.offsetX, evt.offsetY);
  mousePos.x -= dragOffsetX;
  mousePos.y -= dragOffsetY;
  let bounds = {};
  let oldX = selectorRect.x.baseVal.value;
  let oldY = selectorRect.y.baseVal.value;
  let oldW = selectorRect.width.baseVal.value;
  let oldH = selectorRect.height.baseVal.value;

  switch (isDraggingHandle.id) {
    case "nwgrip":
      bounds = {x: mousePos.x, y: mousePos.y, width: oldX + oldW - mousePos.x, height: oldY + oldH - mousePos.y};
      break;

    case "negrip":
      bounds = {x: oldX, y: mousePos.y, width: mousePos.x - oldX, height: oldY + oldH - mousePos.y};
      break;

    case "swgrip":
      bounds = {x: mousePos.x, y: oldY, width: oldX + oldW - mousePos.x, height: mousePos.y - oldY};
      break;

    case "segrip":
      bounds = {x: oldX, y: oldY, width: mousePos.x - oldX, height: mousePos.y - oldY};
      break;
  }
  setSelectorDimensionsTo(bounds);
  // Resize badge
  resizeBadgeTo(bounds);
}

function resizeBadgeTo(bounds) {
  selectedBadge.x.baseVal.value = bounds.x;
  selectedBadge.y.baseVal.value = bounds.y;
  selectedBadge.width.baseVal.value = bounds.width;
  selectedBadge.height.baseVal.value = bounds.height;
}
.canvas {
    background-color: #cecece;
    border: 2px solid #cecece;
    width: 400px;
    height: 400px;
    position: relative;
}

#svg_obj {
  position: absolute;
  width: 100px;
  height: 100px;
  background: yellow;
}

#selector {
  display: none;
}

#selector.show {
  display: block;
}

#selector rect {
  fill: transparent;
  stroke: #f50;
  stroke-width: 1px;
}

#nwgrip, #negrip, #swgrip, #segrip, #ngrip, #egrip, #sgrip, #wgrip {
  fill: #ffffff;
  stroke: #000000;
  stroke-width: 1px;
}
<div id='svg_obj'>
  <svg class="canvas" viewBox="0 0 400 400" width="400" height="400" >
    
    <svg class="badge" width="200" height="200" viewBox="0 0 400 400">
      <g>
        <polygon fill="#21574B" points="342.6,0 324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 57.3,308.3 200,400
                342.6,308.3 "/>
        <polygon fill="#578677" points="342.6,0 324.1,19.8 75.8,19.8 75.8,294.2 57.3,308.3 57.3,0 "/>
        <polygon fill="#8CB2B0" points="324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 75.8,19.8    "/>
      </g>
    </svg>
                
    <svg class="badge" width="200" height="200" viewBox="0 0 400 400">
      <g>
        <polygon fill="#F1F74B" points="342.6,0 324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 57.3,308.3 200,400
                342.6,308.3 "/>
        <polygon fill="#578677" points="342.6,0 324.1,19.8 75.8,19.8 75.8,294.2 57.3,308.3 57.3,0 "/>
        <polygon fill="#8CB2B0" points="324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 75.8,19.8    "/>
      </g>
    </svg>
                
    <svg class="badge" width="200" height="200" viewBox="0 0 400 400">
      <g>
        <polygon fill="#81579B" points="342.6,0 324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 57.3,308.3 200,400
                342.6,308.3 "/>
        <polygon fill="#578677" points="342.6,0 324.1,19.8 75.8,19.8 75.8,294.2 57.3,308.3 57.3,0 "/>
        <polygon fill="#8CB2B0" points="324.1,19.8 324.1,294.2 302.3,308.3 200,374.1 97.7,308.3 75.8,294.2 75.8,19.8    "/>
      </g>
    </svg>
        
    <g id="selector">
      <rect width="20" height="20"/>
      <circle cx="0" cy="0" r="5" id="nwgrip"/>
      <circle cx="20" cy="0" r="5" id="negrip"/>
      <circle cx="0" cy="20" r="5" id="swgrip"/>
      <circle cx="20" cy="20" r="5" id="segrip"/>
    </g>
  </svg>
</div>

Upvotes: 1

Related Questions