Rotate element based cursor position - Javascript

Viewed 21

So i want to make a element rotation like in https://canva.com

It looks like this: https://i.stack.imgur.com/SSSKK.png

I've tried to make it, but there is a glitch when the cursor is on the top area of the element.

This error occurs when the cursor position is the same as the initial position of the text.

Here's my code:

var drag = document.getElementById("draggable-text");
var btn = document.getElementById("rotate-button");
var btnIcon = document.getElementById("svg");
var text = document.getElementById("text");
let activeRotate = false;

// event listener
document.addEventListener("mousedown", dragstart, false);
document.addEventListener("mousemove", dragmove, false);
document.addEventListener("mouseup", dragend, false);

// get the left and top position of the text
function getOffset(element) {
  if (!element.getClientRects().length) {
    return {
      top: 0,
      left: 0
    };
  }

  let rect = element.getBoundingClientRect();
  let win = element.ownerDocument.defaultView;
  return {
    top: rect.top + win.pageYOffset,
    left: rect.left + win.pageXOffset,
  };
}

// on drag start
function dragstart(e) {
  if ((e.target === btn) | (e.target === btnIcon)) {
    activeRotate = true;
  }
}

// on drag move
function dragmove(e) {
  if (activeRotate == true) {
    if (e.type === "touchmove") {
      mouse_x = e.touches[0].clientX;
      mouse_y = e.touches[0].clientY;
    } else {
      mouse_x = e.clientX;
      mouse_y = e.clientY;
    }

    // get the center position of text
    var center_x = getOffset(text).left + text.offsetWidth / 2;
    var center_y = getOffset(text).top + text.offsetHeight / 2;

    var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
    degree = radians * (180 / Math.PI) * -1;

    drag.style.transform = `rotateZ(${degree}deg)`;
  }
}

function dragend() {
  activeRotate = false;
}
<body style="
      display: flex;
      align-items: center;
      justify-content: center;
      padding-top: 50px;
      user-select: none;
    ">
  <div id="draggable-text" style="
        display: flex;
        flex-direction: column;
        align-items: center;
        position: relative;
      ">
    <div id="text-wrapper">
      <!-- text element -->
      <p id="text">texttexttexttexttexttexttexttexttext</p>
    </div>
    <!-- rotate icon -->
    <div id="rotate-button" style="position: absolute; top: 70px">
      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 30px" id="svg">
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
          />
        </svg>
    </div>
  </div>
</body>

Can you help me fix this glitch? Thank you!

1 Answers

You need to move the calculation of center_x and center_y outside of your dragmove(e) function.

There is no reason for center_x or center_y to change when the mouse moves as your goal is to rotate the text about a particular point (as I understand your question).

The algorithm you are using to calculate center_x and center_y is returning different values depending on the amount of rotation of the text (this can be checked by outputting center_x and center_y to the console while moving the mouse cursor).

However, I notice that the solution below does not seem to work properly in full screen mode of the code snippet here on SO (it works OK in minimized mode).

It may be better to calculate the centers in dragstart(e) and you may need to look at the algorithm you are using to calculate the centers more carefully.

var drag = document.getElementById("draggable-text");
var btn = document.getElementById("rotate-button");
var btnIcon = document.getElementById("svg");
var text = document.getElementById("text");
let activeRotate = false;

// event listener
document.addEventListener("mousedown", dragstart, false);
document.addEventListener("mousemove", dragmove, false);
document.addEventListener("mouseup", dragend, false);

// MOVE THESE HERE FROM dragmove(e)
// get the center position of text 
var center_x = getOffset(text).left + text.offsetWidth / 2;
var center_y = getOffset(text).top + text.offsetHeight / 2;

// get the left and top position of the text
function getOffset(element) {
  if (!element.getClientRects().length) {
    return {
      top: 0,
      left: 0
    };
  }

  let rect = element.getBoundingClientRect();
  let win = element.ownerDocument.defaultView;
  return {
    top: rect.top + win.pageYOffset,
    left: rect.left + win.pageXOffset,
  };
}

// on drag start
function dragstart(e) {
  if ((e.target === btn) | (e.target === btnIcon)) {
    activeRotate = true;
  }
}

// on drag move
function dragmove(e) {
  if (activeRotate == true) {
    if (e.type === "touchmove") {
      mouse_x = e.touches[0].clientX;
      mouse_y = e.touches[0].clientY;
    } else {
      mouse_x = e.clientX;
      mouse_y = e.clientY;
    }

    var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
    degree = radians * (180 / Math.PI) * -1;

    drag.style.transform = `rotateZ(${degree}deg)`;
  }
}

function dragend() {
  activeRotate = false;
}
<body style="
      display: flex;
      align-items: center;
      justify-content: center;
      padding-top: 50px;
      user-select: none;
    ">
  <div id="draggable-text" style="
        display: flex;
        flex-direction: column;
        align-items: center;
        position: relative;
      ">
    <div id="text-wrapper">
      <!-- text element -->
      <p id="text">texttexttexttexttexttexttexttexttext</p>
    </div>
    <!-- rotate icon -->
    <div id="rotate-button" style="position: absolute; top: 70px">
      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 30px" id="svg">
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
          />
        </svg>
    </div>
  </div>
</body>

Related