Home > front end >  How to fix aspect ratio of resizable-draggable element?
How to fix aspect ratio of resizable-draggable element?

Time:09-26

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.

CodePudding user response:

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;
                if (newW < minWidth) {
                  newW = minWidth;
                }
            } else {
                newW = initW   rotatedWDiff;
                if (newW < minWidth) {
                  newW = minWidth;
                }
            }
        }
        
        if (yResize) {
            if (top) {
                newH = initH - rotatedHDiff;
                if (newH < minHeight) {
                  newH = minHeight;
                }
            } 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:

CodePudding user response:

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>

  • Related