4

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)?

ps0604
  • 1,227
  • 23
  • 133
  • 330

1 Answers1

3

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>
Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • Christos, it works fine but sometimes is not pixel perfect, the rect is +/- one pixel from the grid, any idea what could be the reason? Maybe when the offset has half pixel? – ps0604 Mar 20 '20 at 19:47
  • @ps0604 thanks for the bounty. It's hard to accomplish that and it needs some math inside `resizemove`. I got it working with perfect snap pixels, but with rounded starting point set for example to `80, 80`, it's not working for `90, 90` like you have it. Check the updated [jsFiddle](https://jsfiddle.net/lytrax/05ofr4e1/52/) to see it working and the maths. – Christos Lytras Mar 20 '20 at 20:39
  • Christos, what about taking the traditional approach and rounding the x/y on `resizeend`? – ps0604 Mar 22 '20 at 23:46
  • @ps0604 we don't deal with floating point numbers here, but with integers. If you check the [jsFiddle](https://jsfiddle.net/lytrax/05ofr4e1/52/), you'll see `Math.floor(Math.abs(event.deltaRect.left) / 10) * 10` and `Math.floor(event.rect.width / 10) * 10` and that is flooring an integer by tenths; for example `102` will end up `100` and `237` will end up `230`. – Christos Lytras Mar 22 '20 at 23:52
  • Christos, please take a [look](https://stackoverflow.com/questions/60973085/making-interact-js-resizable-pixel-perfect) – ps0604 Apr 01 '20 at 14:25