1

I have found a nice solution for Creating a DRR element in javascript here

Currently, I would like to have this feature support to maintaining the aspect ratio.

Here is snippet:

var box = document.getElementById("box");
var boxWrapper = document.getElementById("box-wrapper");
const minWidth = 40;
const minHeight = 40;


var initX, initY, mousePressX, mousePressY, initW, initH, initRotate;

function repositionElement(x, y) {
    boxWrapper.style.left = x + 'px';
    boxWrapper.style.top = y + 'px';
}

function resize(w, h) {
    box.style.width = w + 'px';
    box.style.height = h + 'px';
}


function getCurrentRotation(el) {
    var st = window.getComputedStyle(el, null);
    var tm = st.getPropertyValue("-webkit-transform") ||
        st.getPropertyValue("-moz-transform") ||
        st.getPropertyValue("-ms-transform") ||
        st.getPropertyValue("-o-transform") ||
        st.getPropertyValue("transform")
    "none";
    if (tm != "none") {
        var values = tm.split('(')[1].split(')')[0].split(',');
        var angle = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
        return (angle < 0 ? angle + 360 : angle);
    }
    return 0;
}

function rotateBox(deg) {
    boxWrapper.style.transform = `rotate(${deg}deg)`;
}

// drag support
boxWrapper.addEventListener('mousedown', function (event) {
    if (event.target.className.indexOf("dot") > -1) {
        return;
    }

    initX = this.offsetLeft;
    initY = this.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;


    function eventMoveHandler(event) {
        repositionElement(initX + (event.clientX - mousePressX),
            initY + (event.clientY - mousePressY));
    }

    boxWrapper.addEventListener('mousemove', eventMoveHandler, false);
    window.addEventListener('mouseup', function eventEndHandler() {
        boxWrapper.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);

}, false);
// done drag support

// handle resize
var rightMid = document.getElementById("right-mid");
var leftMid = document.getElementById("left-mid");
var topMid = document.getElementById("top-mid");
var bottomMid = document.getElementById("bottom-mid");

var leftTop = document.getElementById("left-top");
var rightTop = document.getElementById("right-top");
var rightBottom = document.getElementById("right-bottom");
var leftBottom = document.getElementById("left-bottom");

function resizeHandler(event, left = false, top = false, xResize = false, yResize = false) {
    initX = boxWrapper.offsetLeft;
    initY = boxWrapper.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;

    initW = box.offsetWidth;
    initH = box.offsetHeight;

    initRotate = getCurrentRotation(boxWrapper);
    var initRadians = initRotate * Math.PI / 180;
    var cosFraction = Math.cos(initRadians);
    var sinFraction = Math.sin(initRadians);
    function eventMoveHandler(event) {
        var wDiff = (event.clientX - mousePressX);
        var hDiff = (event.clientY - mousePressY);
        var rotatedWDiff = cosFraction * wDiff + sinFraction * hDiff;
        var rotatedHDiff = cosFraction * hDiff - sinFraction * wDiff;

        var newW = initW, newH = initH, newX = initX, newY = initY;

        if (xResize) {
            if (left) {
                newW = initW - rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                  rotatedWDiff = initW - minWidth;
                }
            } else {
                newW = initW + rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                  rotatedWDiff = minWidth - initW;
                }
            }
            newX += 0.5 * rotatedWDiff * cosFraction;
            newY += 0.5 * rotatedWDiff * sinFraction;
        }

        if (yResize) {
            if (top) {
                newH = initH - rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                  rotatedHDiff = initH - minHeight;
                }
            } else {
                newH = initH + rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                  rotatedHDiff = minHeight - initH;
                }
            }
            newX -= 0.5 * rotatedHDiff * sinFraction;
            newY += 0.5 * rotatedHDiff * cosFraction;
        }

        resize(newW, newH);
        repositionElement(newX, newY);
    }


    window.addEventListener('mousemove', eventMoveHandler, false);
    window.addEventListener('mouseup', function eventEndHandler() {
        window.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);
}


rightMid.addEventListener('mousedown', e => resizeHandler(e, false, false, true, false));
leftMid.addEventListener('mousedown', e => resizeHandler(e, true, false, true, false));
topMid.addEventListener('mousedown', e => resizeHandler(e, false, true, false, true));
bottomMid.addEventListener('mousedown', e => resizeHandler(e, false, false, false, true));
leftTop.addEventListener('mousedown', e => resizeHandler(e, true, true, true, true));
rightTop.addEventListener('mousedown', e => resizeHandler(e, false, true, true, true));
rightBottom.addEventListener('mousedown', e => resizeHandler(e, false, false, true, true));
leftBottom.addEventListener('mousedown', e => resizeHandler(e, true, false, true, true));

// handle rotation
var rotate = document.getElementById("rotate");
rotate.addEventListener('mousedown', function (event) {
    // if (event.target.className.indexOf("dot") > -1) {
    //     return;
    // }

    initX = this.offsetLeft;
    initY = this.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;


    var arrow = document.querySelector("#box");
    var arrowRects = arrow.getBoundingClientRect();
    var arrowX = arrowRects.left + arrowRects.width / 2;
    var arrowY = arrowRects.top + arrowRects.height / 2;

    function eventMoveHandler(event) {
        var angle = Math.atan2(event.clientY - arrowY, event.clientX - arrowX) + Math.PI / 2;
        rotateBox(angle * 180 / Math.PI);
    }

    window.addEventListener('mousemove', eventMoveHandler, false);

    window.addEventListener('mouseup', function eventEndHandler() {
        window.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);
}, false);

resize(200, 200);
repositionElement(200, 200);
.box {
    background-color: #00BCD4;
    position: relative;
    user-select: none;
    transform: translate(-50%, -50%);
}

.box-wrapper {
    position: absolute;
    transform-origin: top left;
    user-select: none;
}

.dot {
    height: 10px;
    width: 10px;
    background-color: #1E88E5;
    position: absolute;
    border-radius: 100px;
    border: 1px solid white;
    user-select: none;
}

.dot:hover {
    background-color: #0D47A1;
}

.dot.left-top {
    top: -5px;
    left: -5px;
    /* cursor: nw-resize; */
}

.dot.left-bottom {
    bottom: -5px;
    left: -5px;
    /* cursor: sw-resize; */
}

.dot.right-top {
    top: -5px;
    right: -5px;
    /* cursor: ne-resize; */
}

.dot.right-bottom {
    bottom: -5px;
    right: -5px;
    /* cursor: se-resize; */
}

.dot.top-mid {
    top: -5px;
    left: calc(50% - 5px);
    /* cursor: n-resize; */
}

.dot.left-mid {
    left: -5px;
    top: calc(50% - 5px);
    /* cursor: w-resize; */
}

.dot.right-mid {
    right: -5px;
    top: calc(50% - 5px);
    /* cursor: e-resize; */
}

.dot.bottom-mid {
    bottom: -5px;
    left: calc(50% - 5px);
    /* cursor: s-resize; */
}

.dot.rotate {
    top: -30px;
    left: calc(50% - 5px);
    cursor: url('https://findicons.com/files/icons/1620/crystal_project/16/rotate_ccw.png'), auto;
}

.rotate-link {
    position: absolute;
    width: 1px;
    height: 15px;
    background-color: #1E88E5;
    top: -20px;
    left: calc(50% + 0.5px);
    z-index: -1;
}
<div class="box-wrapper" id="box-wrapper">
    <div class="box" id="box">
        <div class="dot rotate" id="rotate"></div>
        <div class="dot left-top" id="left-top"></div>
        <div class="dot left-bottom" id="left-bottom"></div>
        <div class="dot top-mid" id="top-mid"></div>
        <div class="dot bottom-mid" id="bottom-mid"></div>
        <div class="dot left-mid" id="left-mid"></div>
        <div class="dot right-mid" id="right-mid"></div>
        <div class="dot right-bottom" id="right-bottom"></div>
        <div class="dot right-top" id="right-top"></div>
        <div class="rotate-link"></div>
    </div>
</div>

I am fine if answers are related to maintaining square aspect-ratio only.

Tried solution:

  1. In resize function, set height and width equally.
  2. Reposition element once resize is done and according to position of resized element. But, repositioning behaviour seems glitchy with above solution (missing something / not doing it right).

Goal:

  1. Fix aspect ratio
  2. Keep repositioning behaviour as it is.

Note:

Rotating feature can be ignored, I am looking for a repositioning element accurately.

Thanks! for your appreciable answers.

Radical Edward
  • 2,824
  • 1
  • 10
  • 23

3 Answers3

1

There are two challenges to this question: maintaining an aspect ratio (which is pretty straightforward) and also calculating position based off of anchor points (which can be tricky from the example).

In the original example, the position and magnitude of each dimension is calculated separately (width and x position calculated in the same code block, then height and y position):

        if (xResize) {
            // recalculate width
            if (left) {
                newW = initW - rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                  rotatedWDiff = initW - minWidth;
                }
            } else {
                newW = initW + rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                  rotatedWDiff = minWidth - initW;
                }
            }
            // recalculate position
            newX += 0.5 * rotatedWDiff * cosFraction;
            newY += 0.5 * rotatedWDiff * sinFraction;
        }

        if (yResize) {
            // recalculate height
            if (top) {
                newH = initH - rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                  rotatedHDiff = initH - minHeight;
                }
            } else {
                newH = initH + rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                  rotatedHDiff = minHeight - initH;
                }
            }
            // recalculate position
            newX -= 0.5 * rotatedHDiff * sinFraction;
            newY += 0.5 * rotatedHDiff * cosFraction;
        }

To maintain the aspect ratio, you'd need to constrain the width and height in relation to each other, then reposition based off of those values. Since repositioning based off of height and width are now dependent on each other, this makes the code a little more clunky:

        // calculate new width and height
        if (xResize) {
            if (left) {
                newW = initW - rotatedWDiff;
            } else {
                newW = initW + rotatedWDiff;
            }
            if (newW < minWidth) {
                newW = minWidth;
            }
        }
        
        if (yResize) {
            if (top) {
                newH = initH - rotatedHDiff;
            } else {
                newH = initH + rotatedHDiff;
            }
            if (newH < minHeight) {
                newH = minHeight;
            }
        }
        
        // constrain aspect ratio, if a corner is being dragged
        var scale;
        if (xResize && yResize) {
            scale = Math.max(newW / initW, newH / initH);
            newW = scale * initW;
            newH = scale * initH;
        }

        // recalculate position
        if (xResize) {
            if (left) {
                rotatedWDiff = initW - newW;
            } else {
                rotatedWDiff = newW - initW;
            }
            newX += 0.5 * rotatedWDiff * cosFraction;
            newY += 0.5 * rotatedWDiff * sinFraction;
        }


        if (yResize) {
            if (top) {
                rotatedHDiff = initH - newH;
            } else {
                rotatedHDiff = newH - initH;
            }
            newX -= 0.5 * rotatedHDiff * sinFraction;
            newY += 0.5 * rotatedHDiff * cosFraction;
        }

Altogether it looks like this:

var box = document.getElementById("box");
var boxWrapper = document.getElementById("box-wrapper");
const minWidth = 40;
const minHeight = 40;


var initX, initY, mousePressX, mousePressY, initW, initH, initRotate;

function repositionElement(x, y) {
    boxWrapper.style.left = x + 'px';
    boxWrapper.style.top = y + 'px';
}

function resize(w, h) {
    box.style.width = w + 'px';
    box.style.height = h + 'px';
}


function getCurrentRotation(el) {
    var st = window.getComputedStyle(el, null);
    var tm = st.getPropertyValue("-webkit-transform") ||
        st.getPropertyValue("-moz-transform") ||
        st.getPropertyValue("-ms-transform") ||
        st.getPropertyValue("-o-transform") ||
        st.getPropertyValue("transform")
    "none";
    if (tm != "none") {
        var values = tm.split('(')[1].split(')')[0].split(',');
        var angle = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
        return (angle < 0 ? angle + 360 : angle);
    }
    return 0;
}

function rotateBox(deg) {
    boxWrapper.style.transform = `rotate(${deg}deg)`;
}

// drag support
boxWrapper.addEventListener('mousedown', function (event) {
    if (event.target.className.indexOf("dot") > -1) {
        return;
    }

    initX = this.offsetLeft;
    initY = this.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;


    function eventMoveHandler(event) {
        repositionElement(initX + (event.clientX - mousePressX),
            initY + (event.clientY - mousePressY));
    }

    boxWrapper.addEventListener('mousemove', eventMoveHandler, false);
    window.addEventListener('mouseup', function eventEndHandler() {
        boxWrapper.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);

}, false);
// done drag support

// handle resize
var rightMid = document.getElementById("right-mid");
var leftMid = document.getElementById("left-mid");
var topMid = document.getElementById("top-mid");
var bottomMid = document.getElementById("bottom-mid");

var leftTop = document.getElementById("left-top");
var rightTop = document.getElementById("right-top");
var rightBottom = document.getElementById("right-bottom");
var leftBottom = document.getElementById("left-bottom");

function resizeHandler(event, left = false, top = false, xResize = false, yResize = false) {
    initX = boxWrapper.offsetLeft;
    initY = boxWrapper.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;

    initW = box.offsetWidth;
    initH = box.offsetHeight;

    initRotate = getCurrentRotation(boxWrapper);
    var initRadians = initRotate * Math.PI / 180;
    var cosFraction = Math.cos(initRadians);
    var sinFraction = Math.sin(initRadians);
    function eventMoveHandler(event) {
        var wDiff = (event.clientX - mousePressX);
        var hDiff = (event.clientY - mousePressY);
        var rotatedWDiff = cosFraction * wDiff + sinFraction * hDiff;
        var rotatedHDiff = cosFraction * hDiff - sinFraction * wDiff;

        var newW = initW, newH = initH, newX = initX, newY = initY;

        // calculate new width and height
        if (xResize) {
            if (left) {
                newW = initW - rotatedWDiff;
            } else {
                newW = initW + rotatedWDiff;
            }
            if (newW < minWidth) {
                newW = minWidth;
            }
        }
        
        if (yResize) {
            if (top) {
                newH = initH - rotatedHDiff;
            } else {
                newH = initH + rotatedHDiff;
            }
            if (newH < minHeight) {
                newH = minHeight;
            }
        }
        
        var scale;
        // constrain aspect ratio, if a corner is being dragged
        // can remove the conditional if aspect ratio should always be preserved
        if (xResize && yResize) {
            scale = Math.max(newW / initW, newH / initH);
            newW = scale * initW;
            newH = scale * initH;
        }

        // recalculate position
        if (xResize) {
            if (left) {
                rotatedWDiff = initW - newW;
            } else {
                rotatedWDiff = newW - initW;
            }
            newX += 0.5 * rotatedWDiff * cosFraction;
            newY += 0.5 * rotatedWDiff * sinFraction;
        }


        if (yResize) {
            if (top) {
                rotatedHDiff = initH - newH;
            } else {
                rotatedHDiff = newH - initH;
            }
            newX -= 0.5 * rotatedHDiff * sinFraction;
            newY += 0.5 * rotatedHDiff * cosFraction;
        }

        resize(newW, newH);
        repositionElement(newX, newY);
    }


    window.addEventListener('mousemove', eventMoveHandler, false);
    window.addEventListener('mouseup', function eventEndHandler() {
        window.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);
}


rightMid.addEventListener('mousedown', e => resizeHandler(e, false, false, true, false));
leftMid.addEventListener('mousedown', e => resizeHandler(e, true, false, true, false));
topMid.addEventListener('mousedown', e => resizeHandler(e, false, true, false, true));
bottomMid.addEventListener('mousedown', e => resizeHandler(e, false, false, false, true));
leftTop.addEventListener('mousedown', e => resizeHandler(e, true, true, true, true));
rightTop.addEventListener('mousedown', e => resizeHandler(e, false, true, true, true));
rightBottom.addEventListener('mousedown', e => resizeHandler(e, false, false, true, true));
leftBottom.addEventListener('mousedown', e => resizeHandler(e, true, false, true, true));

// handle rotation
var rotate = document.getElementById("rotate");
rotate.addEventListener('mousedown', function (event) {
    // if (event.target.className.indexOf("dot") > -1) {
    //     return;
    // }

    initX = this.offsetLeft;
    initY = this.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;


    var arrow = document.querySelector("#box");
    var arrowRects = arrow.getBoundingClientRect();
    var arrowX = arrowRects.left + arrowRects.width / 2;
    var arrowY = arrowRects.top + arrowRects.height / 2;

    function eventMoveHandler(event) {
        var angle = Math.atan2(event.clientY - arrowY, event.clientX - arrowX) + Math.PI / 2;
        rotateBox(angle * 180 / Math.PI);
    }

    window.addEventListener('mousemove', eventMoveHandler, false);

    window.addEventListener('mouseup', function eventEndHandler() {
        window.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);
}, false);

resize(300, 200);
repositionElement(200, 200);
.box {
    background-color: #00BCD4;
    position: relative;
    user-select: none;
    transform: translate(-50%, -50%);
}

.box-wrapper {
    position: absolute;
    transform-origin: top left;
    user-select: none;
}

.dot {
    height: 10px;
    width: 10px;
    background-color: #1E88E5;
    position: absolute;
    border-radius: 100px;
    border: 1px solid white;
    user-select: none;
}

.dot:hover {
    background-color: #0D47A1;
}

.dot.left-top {
    top: -5px;
    left: -5px;
    /* cursor: nw-resize; */
}

.dot.left-bottom {
    bottom: -5px;
    left: -5px;
    /* cursor: sw-resize; */
}

.dot.right-top {
    top: -5px;
    right: -5px;
    /* cursor: ne-resize; */
}

.dot.right-bottom {
    bottom: -5px;
    right: -5px;
    /* cursor: se-resize; */
}

.dot.top-mid {
    top: -5px;
    left: calc(50% - 5px);
    /* cursor: n-resize; */
}

.dot.left-mid {
    left: -5px;
    top: calc(50% - 5px);
    /* cursor: w-resize; */
}

.dot.right-mid {
    right: -5px;
    top: calc(50% - 5px);
    /* cursor: e-resize; */
}

.dot.bottom-mid {
    bottom: -5px;
    left: calc(50% - 5px);
    /* cursor: s-resize; */
}

.dot.rotate {
    top: -30px;
    left: calc(50% - 5px);
    cursor: url('https://findicons.com/files/icons/1620/crystal_project/16/rotate_ccw.png'), auto;
}

.rotate-link {
    position: absolute;
    width: 1px;
    height: 15px;
    background-color: #1E88E5;
    top: -20px;
    left: calc(50% + 0.5px);
    z-index: -1;
}
<div class="box-wrapper" id="box-wrapper">
    <div class="box" id="box">
        <div class="dot rotate" id="rotate"></div>
        <div class="dot left-top" id="left-top"></div>
        <div class="dot left-bottom" id="left-bottom"></div>
        <div class="dot top-mid" id="top-mid"></div>
        <div class="dot bottom-mid" id="bottom-mid"></div>
        <div class="dot left-mid" id="left-mid"></div>
        <div class="dot right-mid" id="right-mid"></div>
        <div class="dot right-bottom" id="right-bottom"></div>
        <div class="dot right-top" id="right-top"></div>
        <div class="rotate-link"></div>
    </div>
</div>
Steve
  • 10,435
  • 15
  • 21
1

You could also relay on CSS rule aspect-ratio :

If you use minWidth and minHeight instead to resize the element it might help to reset and keep ratio.

example below , with a a few reset on resize(X,X) function to test behavior :)

const box = document.getElementById("box");
var boxWrapper = document.getElementById("box-wrapper");
const minWidth = 40;
const minHeight = 40;

function resetSize() {// shrinks it to the min size set (unless content inside takes bigger room
  box.style.width = '0';
  box.style.height = '0';
}

function setCssRatio() {// get ratio from its size
  let bH = box.offsetHeight;
  let bW = box.offsetWidth;
  let dif = bH / bW;
  document.documentElement.style.setProperty("--ratio", "1/" + dif);
  document.querySelector('#box p').innerHTML = "calculated ratio: <br> 1 / " + dif.toFixed(1);
  box.style.width = 'auto';
  box.style.height = 'auto';
}

var initX, initY, mousePressX, mousePressY, initW, initH, initRotate;

function repositionElement(x, y) {
  boxWrapper.style.left = x + 'px';
  boxWrapper.style.top = y + 'px';
}

function resize(w, h) {

  box.style.minWidth = w + 'px';
  box.style.minHeight = h + 'px';
  setCssRatio();
}


function getCurrentRotation(el) {
  var st = window.getComputedStyle(el, null);
  var tm = st.getPropertyValue("-webkit-transform") ||
    st.getPropertyValue("-moz-transform") ||
    st.getPropertyValue("-ms-transform") ||
    st.getPropertyValue("-o-transform") ||
    st.getPropertyValue("transform")
  "none";
  if (tm != "none") {
    var values = tm.split('(')[1].split(')')[0].split(',');
    var angle = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
    return (angle < 0 ? angle + 360 : angle);
  }
  return 0;
}

function rotateBox(deg) {
  boxWrapper.style.transform = `rotate(${deg}deg)`;
}

// drag support
boxWrapper.addEventListener('mousedown', function(event) {
  if (event.target.className.indexOf("dot") > -1) {
    return;
  }

  initX = this.offsetLeft;
  initY = this.offsetTop;
  mousePressX = event.clientX;
  mousePressY = event.clientY;


  function eventMoveHandler(event) {
    repositionElement(initX + (event.clientX - mousePressX),
      initY + (event.clientY - mousePressY));
  }

  boxWrapper.addEventListener('mousemove', eventMoveHandler, false);
  window.addEventListener('mouseup', function eventEndHandler() {
    boxWrapper.removeEventListener('mousemove', eventMoveHandler, false);
    window.removeEventListener('mouseup', eventEndHandler);
  }, false);

}, false);
// done drag support

// handle resize
var rightMid = document.getElementById("right-mid");
var leftMid = document.getElementById("left-mid");
var topMid = document.getElementById("top-mid");
var bottomMid = document.getElementById("bottom-mid");

var leftTop = document.getElementById("left-top");
var rightTop = document.getElementById("right-top");
var rightBottom = document.getElementById("right-bottom");
var leftBottom = document.getElementById("left-bottom");

function resizeHandler(event, left = false, top = false, xResize = false, yResize = false) {
  initX = boxWrapper.offsetLeft;
  initY = boxWrapper.offsetTop;
  mousePressX = event.clientX;
  mousePressY = event.clientY;

  initW = box.offsetWidth;
  initH = box.offsetHeight;

  initRotate = getCurrentRotation(boxWrapper);
  var initRadians = initRotate * Math.PI / 180;
  var cosFraction = Math.cos(initRadians);
  var sinFraction = Math.sin(initRadians);

  function eventMoveHandler(event) {
    var wDiff = (event.clientX - mousePressX);
    var hDiff = (event.clientY - mousePressY);
    var rotatedWDiff = cosFraction * wDiff + sinFraction * hDiff;
    var rotatedHDiff = cosFraction * hDiff - sinFraction * wDiff;

    var newW = initW,
      newH = initH,
      newX = initX,
      newY = initY;

    if (xResize) {
      if (left) {
        newW = initW - rotatedWDiff;
        if (newW < minWidth) {
          newW = minWidth;
          rotatedWDiff = initW - minWidth;
        }
      } else {
        newW = initW + rotatedWDiff;
        if (newW < minWidth) {
          newW = minWidth;
          rotatedWDiff = minWidth - initW;
        }
      }
      newX += 0.5 * rotatedWDiff * cosFraction;
      newY += 0.5 * rotatedWDiff * sinFraction;
    }

    if (yResize) {
      if (top) {
        newH = initH - rotatedHDiff;
        if (newH < minHeight) {
          newH = minHeight;
          rotatedHDiff = initH - minHeight;
        }
      } else {
        newH = initH + rotatedHDiff;
        if (newH < minHeight) {
          newH = minHeight;
          rotatedHDiff = minHeight - initH;
        }
      }
      newX -= 0.5 * rotatedHDiff * sinFraction;
      newY += 0.5 * rotatedHDiff * cosFraction;
    }

    resize(newW, newH);
    repositionElement(newX, newY);
  }


  window.addEventListener('mousemove', eventMoveHandler, false);
  window.addEventListener('mouseup', function eventEndHandler() {
    window.removeEventListener('mousemove', eventMoveHandler, false);
    window.removeEventListener('mouseup', eventEndHandler);
  }, false);
}


rightMid.addEventListener('mousedown', e => resizeHandler(e, false, false, true, false));
leftMid.addEventListener('mousedown', e => resizeHandler(e, true, false, true, false));
topMid.addEventListener('mousedown', e => resizeHandler(e, false, true, false, true));
bottomMid.addEventListener('mousedown', e => resizeHandler(e, false, false, false, true));
leftTop.addEventListener('mousedown', e => resizeHandler(e, true, true, true, true));
rightTop.addEventListener('mousedown', e => resizeHandler(e, false, true, true, true));
rightBottom.addEventListener('mousedown', e => resizeHandler(e, false, false, true, true));
leftBottom.addEventListener('mousedown', e => resizeHandler(e, true, false, true, true));

// handle rotation
var rotate = document.getElementById("rotate");
rotate.addEventListener('mousedown', function(event) {
  // if (event.target.className.indexOf("dot") > -1) {
  //     return;
  // }

  initX = this.offsetLeft;
  initY = this.offsetTop;
  mousePressX = event.clientX;
  mousePressY = event.clientY;


  var arrow = document.querySelector("#box");
  var arrowRects = arrow.getBoundingClientRect();
  var arrowX = arrowRects.left + arrowRects.width / 2;
  var arrowY = arrowRects.top + arrowRects.height / 2;

  function eventMoveHandler(event) {
    var angle = Math.atan2(event.clientY - arrowY, event.clientX - arrowX) + Math.PI / 2;
    rotateBox(angle * 180 / Math.PI);
  }

  window.addEventListener('mousemove', eventMoveHandler, false);

  window.addEventListener('mouseup', function eventEndHandler() {
    window.removeEventListener('mousemove', eventMoveHandler, false);
    window.removeEventListener('mouseup', eventEndHandler);
  }, false);
}, false);

resize(220, 300);
repositionElement(200, 200);
.box {
  background-color: #00BCD4;
  position: relative;
  user-select: none;
  transform: translate(-50%, -50%);
}

.box-wrapper {
  position: absolute;
  transform-origin: top left;
  user-select: none;
}

.dot {
  height: 10px;
  width: 10px;
  background-color: #1E88E5;
  position: absolute;
  border-radius: 100px;
  border: 1px solid white;
  user-select: none;
}

.dot:hover {
  background-color: #0D47A1;
}

.dot.left-top {
  top: -5px;
  left: -5px;
  /* cursor: nw-resize; */
}

.dot.left-bottom {
  bottom: -5px;
  left: -5px;
  /* cursor: sw-resize; */
}

.dot.right-top {
  top: -5px;
  right: -5px;
  /* cursor: ne-resize; */
}

.dot.right-bottom {
  bottom: -5px;
  right: -5px;
  /* cursor: se-resize; */
}

.dot.top-mid {
  top: -5px;
  left: calc(50% - 5px);
  /* cursor: n-resize; */
}

.dot.left-mid {
  left: -5px;
  top: calc(50% - 5px);
  /* cursor: w-resize; */
}

.dot.right-mid {
  right: -5px;
  top: calc(50% - 5px);
  /* cursor: e-resize; */
}

.dot.bottom-mid {
  bottom: -5px;
  left: calc(50% - 5px);
  /* cursor: s-resize; */
}

.dot.rotate {
  top: -30px;
  left: calc(50% - 5px);
  cursor: url('https://findicons.com/files/icons/1620/crystal_project/16/rotate_ccw.png'), auto;
}

.rotate-link {
  position: absolute;
  width: 1px;
  height: 15px;
  background-color: #1E88E5;
  top: -20px;
  left: calc(50% + 0.5px);
  z-index: -1;
}

#box {
  aspect-ratio: var(--ratio);
  display: flex;
}

#box p {
  margin: auto;
  text-align: center;
}
<div class="box-wrapper" id="box-wrapper">
  <div class="box" id="box">
    <p></p>
    <div class="dot rotate" id="rotate"></div>
    <div class="dot left-top" id="left-top"></div>
    <div class="dot left-bottom" id="left-bottom"></div>
    <div class="dot top-mid" id="top-mid"></div>
    <div class="dot bottom-mid" id="bottom-mid"></div>
    <div class="dot left-mid" id="left-mid"></div>
    <div class="dot right-mid" id="right-mid"></div>
    <div class="dot right-bottom" id="right-bottom"></div>
    <div class="dot right-top" id="right-top"></div>
    <div class="rotate-link"></div>
  </div>
</div>
<button onclick="resize(150,150);resetSize();setCssRatio();return false;">set a square</button>
<button onclick="resize(200,100);;resetSize();setCssRatio();return false;">set a 200x100</button>
<button onclick="resize(100,200);;resetSize();setCssRatio();return false;">set a 100x200</button>
G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
  • I'm convinced that using `aspect-ratio` css surely makes this easier to preserve aspect ratio, but I would like to have repositioning behaviour as it is. liked it! – Radical Edward Sep 26 '21 at 15:30
0

in eventMoveHandler set aspect ratio and remove xResize and yResize check it will work.

var box = document.getElementById("box");
var boxWrapper = document.getElementById("box-wrapper");
const minWidth = 40;
const minHeight = 40;


var initX, initY, mousePressX, mousePressY, initW, initH, initRotate;

function repositionElement(x, y) {
    boxWrapper.style.left = x + 'px';
    boxWrapper.style.top = y + 'px';
}

function resize(w, h) {
    box.style.width = w + 'px';
    box.style.height = h + 'px';
}


function getCurrentRotation(el) {
    var st = window.getComputedStyle(el, null);
    var tm = st.getPropertyValue("-webkit-transform") ||
        st.getPropertyValue("-moz-transform") ||
        st.getPropertyValue("-ms-transform") ||
        st.getPropertyValue("-o-transform") ||
        st.getPropertyValue("transform")
    "none";
    if (tm != "none") {
        var values = tm.split('(')[1].split(')')[0].split(',');
        var angle = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
        return (angle < 0 ? angle + 360 : angle);
    }
    return 0;
}

function rotateBox(deg) {
    boxWrapper.style.transform = `rotate(${deg}deg)`;
}

// drag support
boxWrapper.addEventListener('mousedown', function (event) {
    if (event.target.className.indexOf("dot") > -1) {
        return;
    }

    initX = this.offsetLeft;
    initY = this.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;


    function eventMoveHandler(event) {
        repositionElement(initX + (event.clientX - mousePressX),
            initY + (event.clientY - mousePressY));
    }

    boxWrapper.addEventListener('mousemove', eventMoveHandler, false);
    window.addEventListener('mouseup', function eventEndHandler() {
        boxWrapper.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);

}, false);
// done drag support

// handle resize
var rightMid = document.getElementById("right-mid");
var leftMid = document.getElementById("left-mid");
var topMid = document.getElementById("top-mid");
var bottomMid = document.getElementById("bottom-mid");

var leftTop = document.getElementById("left-top");
var rightTop = document.getElementById("right-top");
var rightBottom = document.getElementById("right-bottom");
var leftBottom = document.getElementById("left-bottom");

function resizeHandler(event, left = false, top = false, xResize = false, yResize = false) {
    initX = boxWrapper.offsetLeft;
    initY = boxWrapper.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;

    initW = box.offsetWidth;
    initH = box.offsetHeight;
    acptRatio = initW/initH;
    initRotate = getCurrentRotation(boxWrapper);
    var initRadians = initRotate * Math.PI / 180;
    var cosFraction = Math.cos(initRadians);
    var sinFraction = Math.sin(initRadians);
    function eventMoveHandler(event) {
        var wDiff = (event.clientX - mousePressX);
        var hDiff = (event.clientY - mousePressY);
        if(wDiff > hDiff) hDiff = wDiff/acptRatio;
        else wDiff = hDiff*acptRatio;
        var rotatedWDiff = cosFraction * wDiff + sinFraction * hDiff;
        var rotatedHDiff = cosFraction * hDiff - sinFraction * wDiff;

        var newW = initW, newH = initH, newX = initX, newY = initY;

        //if (xResize || true) {
            if (left) {
                newW = initW - rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                  rotatedWDiff = initW - minWidth;
                }
            } else {
                newW = initW + rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                  rotatedWDiff = minWidth - initW;
                }
            }
            newX += 0.5 * rotatedWDiff * cosFraction;
            newY += 0.5 * rotatedWDiff * sinFraction;
       // }

        //if (yResize || true) {
            if (top) {
                newH = initH - rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                  rotatedHDiff = initH - minHeight;
                }
            } else {
                newH = initH + rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                  rotatedHDiff = minHeight - initH;
                }
            }
            newX -= 0.5 * rotatedHDiff * sinFraction;
            newY += 0.5 * rotatedHDiff * cosFraction;
       // }

        resize(newW, newH);
        repositionElement(newX, newY);
    }


    window.addEventListener('mousemove', eventMoveHandler, false);
    window.addEventListener('mouseup', function eventEndHandler() {
        window.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);
}


rightMid.addEventListener('mousedown', e => resizeHandler(e, false, false, true, false));
leftMid.addEventListener('mousedown', e => resizeHandler(e, true, false, true, false));
topMid.addEventListener('mousedown', e => resizeHandler(e, false, true, false, true));
bottomMid.addEventListener('mousedown', e => resizeHandler(e, false, false, false, true));
leftTop.addEventListener('mousedown', e => resizeHandler(e, true, true, true, true));
rightTop.addEventListener('mousedown', e => resizeHandler(e, false, true, true, true));
rightBottom.addEventListener('mousedown', e => resizeHandler(e, false, false, true, true));
leftBottom.addEventListener('mousedown', e => resizeHandler(e, true, false, true, true));

// handle rotation
var rotate = document.getElementById("rotate");
rotate.addEventListener('mousedown', function (event) {
    // if (event.target.className.indexOf("dot") > -1) {
    //     return;
    // }

    initX = this.offsetLeft;
    initY = this.offsetTop;
    mousePressX = event.clientX;
    mousePressY = event.clientY;


    var arrow = document.querySelector("#box");
    var arrowRects = arrow.getBoundingClientRect();
    var arrowX = arrowRects.left + arrowRects.width / 2;
    var arrowY = arrowRects.top + arrowRects.height / 2;

    function eventMoveHandler(event) {
        var angle = Math.atan2(event.clientY - arrowY, event.clientX - arrowX) + Math.PI / 2;
        rotateBox(angle * 180 / Math.PI);
    }

    window.addEventListener('mousemove', eventMoveHandler, false);

    window.addEventListener('mouseup', function eventEndHandler() {
        window.removeEventListener('mousemove', eventMoveHandler, false);
        window.removeEventListener('mouseup', eventEndHandler);
    }, false);
}, false);

resize(200, 200);
repositionElement(200, 200);
.box {
    background-color: #00BCD4;
    position: relative;
    user-select: none;
    transform: translate(-50%, -50%);
}

.box-wrapper {
    position: absolute;
    transform-origin: top left;
    user-select: none;
}

.dot {
    height: 10px;
    width: 10px;
    background-color: #1E88E5;
    position: absolute;
    border-radius: 100px;
    border: 1px solid white;
    user-select: none;
}

.dot:hover {
    background-color: #0D47A1;
}

.dot.left-top {
    top: -5px;
    left: -5px;
    /* cursor: nw-resize; */
}

.dot.left-bottom {
    bottom: -5px;
    left: -5px;
    /* cursor: sw-resize; */
}

.dot.right-top {
    top: -5px;
    right: -5px;
    /* cursor: ne-resize; */
}

.dot.right-bottom {
    bottom: -5px;
    right: -5px;
    /* cursor: se-resize; */
}

.dot.top-mid {
    top: -5px;
    left: calc(50% - 5px);
    /* cursor: n-resize; */
}

.dot.left-mid {
    left: -5px;
    top: calc(50% - 5px);
    /* cursor: w-resize; */
}

.dot.right-mid {
    right: -5px;
    top: calc(50% - 5px);
    /* cursor: e-resize; */
}

.dot.bottom-mid {
    bottom: -5px;
    left: calc(50% - 5px);
    /* cursor: s-resize; */
}

.dot.rotate {
    top: -30px;
    left: calc(50% - 5px);
    cursor: url('https://findicons.com/files/icons/1620/crystal_project/16/rotate_ccw.png'), auto;
}

.rotate-link {
    position: absolute;
    width: 1px;
    height: 15px;
    background-color: #1E88E5;
    top: -20px;
    left: calc(50% + 0.5px);
    z-index: -1;
}
<div class="box-wrapper" id="box-wrapper">
    <div class="box" id="box">
        <div class="dot rotate" id="rotate"></div>
        <div class="dot left-top" id="left-top"></div>
        <div class="dot left-bottom" id="left-bottom"></div>
        <div class="dot top-mid" id="top-mid"></div>
        <div class="dot bottom-mid" id="bottom-mid"></div>
        <div class="dot left-mid" id="left-mid"></div>
        <div class="dot right-mid" id="right-mid"></div>
        <div class="dot right-bottom" id="right-bottom"></div>
        <div class="dot right-top" id="right-top"></div>
        <div class="rotate-link"></div>
    </div>
</div>
Zeeshan Anjum
  • 944
  • 8
  • 16