Resizing a rotated item with javascript (interact.js)
Asked Answered
A

3

15

I have spent many days trying to make an item resizable that is rotated with interact.js.

This is the code that I have at this moment, I will try to explain the concept.

We have a selector item for two reasons, because the container could be scaled with css transform (like a zoom), and we need to have the selector outside and because we have a multiselection, and the selector grow if I have two rectangle selected, but in this case this is not the main problem and we have calculated the scaled proportion without problems and other things.

When the selector is resize, it take the rectangle, and make the same with the width, height, left, top and rotation.

Javascript:

// TAP - CLICK EVENT (just for positioning the selector)
interact('#rectangle').on('tap', event => {
  console.log('Tap Box!');
  event.stopPropagation();
  const $rectangleCloned = $('#rectangle').clone();
  const previousTransform = $rectangleCloned.css('transform');
  $rectangleCloned.css('transform', 'none');
  $rectangleCloned.css('opacity', '0');
  $rectangleCloned.css('display', 'block');


  $('#container').append($rectangleCloned);
  const values = $rectangleCloned[0].getBoundingClientRect();
  // This is just a trick for fast implementation:
  $('#selector').css('top', values.y);
  $('#selector').css('left', values.x);
  $('#selector').css('width', values.width);
  $('#selector').css('height', values.height);
  $('#selector').css('transform', previousTransform);

  $rectangleCloned.remove();
  return values;
});


interact('.pointer9').draggable({
  max: 1,
  onmove: event => {
    const angleDeg =
      Math.atan2(
        centerRotate.posY - event.pageY,
        centerRotate.posX - event.pageX
      ) *
      180 /
      Math.PI;

    console.log(this.rotate);
    const prevAngle = this.rotate - angleInitial;
    const angle = parseInt(angleDeg) + prevAngle;
    this.$rectangle.css({
      transform: 'rotate(' + angle + 'deg)'
    });
    this.$selector.css({
      transform: 'rotate(' + angle + 'deg)'
    });
  },
  onstart: event => {
    const data = event.interactable.getRect(event.target.parentNode);
    this.centerRotate = {
      posX: data.left + data.width / 2,
      posY: data.top + data.height / 2
    };
    this.angleInitial =
      Math.atan2(
        centerRotate.posY - event.pageY,
        centerRotate.posX - event.pageX
      ) *
      180 /
      Math.PI;
    this.$rectangle = $('#rectangle');
    this.$selector = $('#selector');
    this.rotate = $rectangle.attr('angle') || 0;
  },
  onend: event => {
    const $box = $('#selector');
    const matrix = $box.css('transform');
    const values = matrix
      .split('(')[1]
      .split(')')[0]
      .split(',');

    var a = values[0];
    var b = values[1];
    var angle = Math.round(Math.atan2(b, a) * (180 / Math.PI));
    $rectangle.attr('angle', angle);

  }
});


interact('#selector')
  .resizable({
    // resize from all edges and corners
    edges: {
      left: true,
      right: true,
      bottom: true,
      top: true
    },

    // keep the edges inside the parent
    restrictEdges: {
      outer: 'parent',
      endOnly: true,
    },

    // minimum size
    restrictSize: {
      min: {
        width: 100,
        height: 50
      },
    },

    inertia: true,
  })
  .on('resizemove', function(event) {
    var target = event.target,
      x = parseFloat($(target).offset().left) || 0,
      y = parseFloat($(target).offset().top) || 0;

    // update the element's style
    target.style.width = event.rect.width + 'px';
    target.style.height = event.rect.height + 'px';

    // translate when resizing from top or left edges
    x += event.deltaRect.left;
    y += event.deltaRect.top;

    target.style.left = x + 'px';
    target.style.top = y + 'px';

    $('#rectangle')[0].style.left = target.style.left;
    $('#rectangle')[0].style.top = target.style.top;

    $('#rectangle')[0].style.width = target.style.width;
    $('#rectangle')[0].style.height = target.style.height;

    target.setAttribute('data-x', x);
    target.setAttribute('data-y', y);

  });

CSS:

#container {
  width: 500px;
  height: 400px;
  top: 0;
  left: 0;
  position: absolute;
  background-color: #CCC;
}

#rectangle {
  top: 50px;
  left: 50px;
  width: 120px;
  height: 60px;
  background-color: red;
  position: absolute;
}

#selector {
  display: inline-block;
  position: absolute;
  pointer-events: none;
  z-index: 9999;
  top: -1000px;
  /*Not showing at start*/
}

#selector .pointers {
  display: inline-block;
  position: absolute;
  z-index: 2;
  width: 10px;
  height: 10px;
  pointer-events: all;
}

#selector .pointers .point {
  width: 10px;
  height: 10px;
  background-color: #fff;
  border: 2px solid rgba(0, 0, 0, 0.9);
  border-radius: 50%;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

#selector .pointers.pointer1 {
  top: -5px;
  left: -5px;
}

#selector .pointers.pointer2 {
  bottom: -5px;
  left: -5px;
}

#selector .pointers.pointer3 {
  top: -5px;
  right: -5px;
}

#selector .pointers.pointer4 {
  bottom: -5px;
  right: -5px;
}

#selector .pointers.pointer-north {
  top: -5px;
  left: calc(50% - 5px);
}

#selector .pointers.pointer-south {
  bottom: -5px;
  left: calc(50% - 5px);
}

#selector .pointers.pointer-east {
  right: -5px;
  top: calc(50% - 5px);
}

#selector .pointers.pointer-west {
  left: -5px;
  top: calc(50% - 5px);
}

#selector .pointer-rotate {
  border: 2px solid rgba(0, 0, 0, 0.9);
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  border-radius: 50%;
  cursor: rotate;
}

#selector .pointer9 {
  bottom: -70px;
  left: calc(50% - 11px);
  display: inline-block;
  width: 20px;
  height: 20px;
  background-color: #fff;
  pointer-events: all;
  position: absolute;
}

#selector .rotate-line {
  border-left: 1px dashed #5f5f5f;
  height: 40px;
  position: absolute;
  top: -40px;
  left: calc(50% - 1px);
  width: 1px;
}

HTML:

<div id="container">
  <div id="rectangle">
  </div>
  <div id="selector">
    <div class="pointers pointer1">
      <div class="point"></div>
    </div>
    <div class="pointers pointer2">
      <div class="point">
      </div>
    </div>
    <div class="pointers pointer3">
      <div class="point">
      </div>
    </div>
    <div class="pointers pointer4">
      <div class="point">
      </div>
    </div>
    <div class="pointers pointer-north">
      <div class="point">
      </div>
    </div>
    <div class="pointers pointer-east">
      <div class="point">

      </div>
    </div>
    <div class="pointers pointer-south">
      <div class="point">
      </div>
    </div>
    <div class="pointers pointer-west">
      <div class="point">
      </div>
    </div>
    <span class="topline lines-resize" />
    <span class="rightline lines-resize" />
    <span class="botline lines-resize" />
    <span class="leftline lines-resize" />
    <div class="pointer-rotate pointer9" />
    <div class="rotate-line" />
  </div>
</div>

Fiddle for testing:

https://jsfiddle.net/ub70028c/46/

I have read about other people trying to make the same without not results...

Thanks!

Aegis answered 10/4, 2018 at 16:11 Comment(0)
L
5

I checked your code and a similar library for resizable and rotatable and I figure out your problem.

First, checking similar library:

Please see this fiddle that I created by jquery.freetrans.js.

If you inspect on <div class="shape">, you can see

transform: matrix(1, 0, 0, 1, 0, 0);

If you rotate it, transform changed like below:

transform: matrix(0.997373, -0.0724379, 0.0724379, 0.997373, 0, 0);

In similar case, your code uses transform that at first, it doesn't transform and after rotating, it has like below:

transform: rotate(-2.49576deg);

If you can use matrix instead of rotate in transform, your code will work properly. If you can't change it, you can use similar library like jquery.freetrans.jsthat work properly with rotate and resize together.

Lauber answered 15/4, 2018 at 6:3 Comment(2)
I want the maths to do it... but thanks. We are close to get it working... even with multiple items at the same time. We are working in this moment with a mathematician and he is supporting us to make the code works. He is working with a lot of trigonometric.Aegis
@Aegis have you reached a solution for this?Oreopithecus
E
0

https://github.com/taye/interact.js/issues/569

https://github.com/taye/interact.js/issues/499

https://github.com/taye/interact.js/issues/394

I am afraid you have chosen a library whose author has clearly stated his intent

There's no built-in way. As I mentioned in #137 I'm not really interested in handling scaled or rotated elements

So the question you should ask yourself is

Do I want to find a workaround to make this library work or choose a different library perhaps?

Update-1: 28-Apr-2018

In case you want to do it in canvas instead of normal elements then I found fabric.js a good option

FabricJS

Errick answered 12/4, 2018 at 19:36 Comment(3)
I would suggest you pickup another library even if the math needs to be figured out. Because you don't want to pickup something which will not go the way where you want to. I will see if I can post more details about the equationsErrick
Thanks! I don't know any library that do that, and jquery-free-transform is very old... and the maths is very "harcoded".Aegis
Another comment, Interact.js for us is the best, because it give you all the data for make your own operations... delta, edges, etc.Aegis
A
0

we are very close to finish the work after five days... we need to optimice all the mathematical calculations... but yes, this is what I was looking for:

enter image description here

Sorry, but we don't have the code ready... I will post all with comments for other people.

Comments: For a mathematician, this task is not very complex because all the angles are rectangular (90º). I will try to make a PR to the Interact.js, even to other libraries to implement this feature by default. Hope this work help to other developers ;)

Aegis answered 17/4, 2018 at 16:38 Comment(3)
can you share the math for the transform matrix. I am also stuck :(Elenor
Hi @chemitaxis, have you find suitable solution for interact.js? If yes, could you share your workaround? Thanks!Sair
Yes please, I would love a solution to this using InteractJS - it does everything I want, but the 'flicker` caused by resizing a rotated element is frustration and feels clunky.Steve

© 2022 - 2024 — McMap. All rights reserved.