Search code examples
javascriptcsssvginteract.js

interact.js SVG rect snapping to a div with margins


In this jsFiddle I have an SVG interact.js rect that when resized it snaps to a grid.

This works fine until I start changing margins from zero to a number. The rect is inside a div #mysvg and if I change the margins of the div, the rect snaps incorrectly (there's a shift).

Try changing in the CSS the margins from:

#mysvg { 
   margin-top: 0px; 
   margin-left: 0px; 
}

To:

#mysvg { 
   margin-top: 12px; 
   margin-left: 12px; 
}

Then rerun the jsFiddle and you'll see the problem.

Similar issue happens when the body margin is incremented from zero.

How to fix this problem? Is there a way to make the interact.js resize relative to the div, ignoring its margin or where the div is positioned on the page (for example, the div may be located inside another div)?


Solution

  • There is an offset property in interact.snappers.grid which you can use to offset the grid snap:

    modifiers: [
      interact.modifiers.snap({
        targets: [
          interact.snappers.grid({
            x: 20,
            y: 20,
    
            // Here set the offset x, y
            // to the margins top and left of the SVG
            offset: { x: 12, y: 12 }
    
          }),
        ]
      })
    ]
    

    You can check it working with #mysvg margin-top and margin-left set to 12px in this jsFiddle, or run the below code snippet:

    var svg = document.getElementById('mysvg');
    
    // draw vertical lines
    var gridSize = 20;
    for (var i=0;i < 100;i++){
      var line = document.createElementNS("http://www.w3.org/2000/svg", "line");    
      svg.appendChild(line);
      line.setAttribute("x1", (i + 1) * gridSize)
      line.setAttribute("y1", 0)
      line.setAttribute("x2", (i + 1) * gridSize)
      line.setAttribute("y2", 500)
      line.setAttribute("stroke-width", 1)
      line.setAttribute("stroke", 'gray');
    }
            
    // draw vertical lines
    for (var i=0;i < 100;i++){
      var line = document.createElementNS("http://www.w3.org/2000/svg", "line");    
      svg.appendChild(line);
      line.setAttribute("x1", 0)
      line.setAttribute("y1", (i + 1) * gridSize)
      line.setAttribute("x2", 2000)
      line.setAttribute("y2", (i + 1) * gridSize)
      line.setAttribute("stroke-width", 1)
      line.setAttribute("stroke", 'gray');
    }
    
    var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
    svg.appendChild(rect);
    rect.setAttribute('x', 90);
    rect.setAttribute('y', 90);
    rect.setAttribute('width', 100);
    rect.setAttribute('height', 100);
    rect.setAttribute('class', 'resize-me');
    rect.setAttribute('stroke-width', 2);
    rect.setAttribute('stroke', 'black');
    rect.setAttribute('fill', 'orange');
    
    interact('.resize-me')
      .resizable({
        edges: { left: true, right: true, bottom: true, top: true },
        margin: 3,
        modifiers: [
          interact.modifiers.snap({
            targets: [
              interact.snappers.grid({
                x: 20,
                y: 20,
    
                // Here set the offset x, y
                // to the margins top and left of the SVG
                offset: { x: 12, y: 12 }
    
              }),
            ]
          })
        ]
      })
      .on('resizemove', function(event) {
        var target = event.target;
        var x = (parseFloat(target.getAttribute('endx')) || 0)
        var y = (parseFloat(target.getAttribute('endy')) || 0)
    
        target.setAttribute('width', event.rect.width);
        target.setAttribute('height', event.rect.height);
    
        x += event.deltaRect.left
        y += event.deltaRect.top
        target.setAttribute('transform', 'translate(' + x + ', ' + y + ')')
    
        target.setAttribute('endx', x)
        target.setAttribute('endy', y)
      });
    svg {
      width: 100%;
      height: 240px;
      
      -ms-touch-action: none;
      touch-action: none;
      box-sizing: border-box;
    }
    
    body { margin: 0px }
    
    #mysvg { 
       margin-top: 12px; 
       margin-left: 12px; 
    }
    <script src="https://cdn.jsdelivr.net/npm/interactjs@latest/dist/interact.min.js"></script>
    
    <svg id="mysvg"></svg>