Monday, June 3, 2024
 Popular · Latest · Hot · Upcoming
20
rated 0 times [  21] [ 1]  / answers: 1 / hits: 5867  / 4 Years ago, thu, november 5, 2020, 12:00:00

I've been trying to create something like this in Javascript:


enter


As you can see, the container can be dragged, rotated and resized. Most of the things work fine but the resizing of container when it is rotated produce weird output.


I expect this to happen:


enter


Instead I get this:


enter


Here's the full code:


https://jsfiddle.net/c0krownz/


or,




var box = document.getElementById(box);
var boxWrapper = document.getElementById(box-wrapper);

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

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

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


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) + 'px',
initY + (event.clientY - mousePressY) + 'px');
}

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

window.addEventListener('mouseup', function () {
boxWrapper.removeEventListener('mousemove', eventMoveHandler, false);
}, 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);

function eventMoveHandler(event) {
var wDiff = event.clientX - mousePressX;
var hDiff = event.clientY - mousePressY;

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

if (xResize) {
if (left) {
newW = initW - wDiff;
newX = initX + wDiff;
} else {
newW = initW + wDiff;
}
}

if (yResize) {
if (top) {
newH = initH - hDiff;
newY = initY + hDiff;
} else {
newH = initH + hDiff;
}
}

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

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

window.addEventListener('mouseup', function () {
window.removeEventListener('mousemove', eventMoveHandler, false);
}, 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 () {
window.removeEventListener('mousemove', eventMoveHandler, false);
}, false);

}, false);



resize(300, 200);
repositionElement(100, 100);

.box {
background-color: #00BCD4;
position: relative;
user-select: none;
}

.box-wrapper {
position: absolute;
transform-origin: center center;
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>




More From » html

 Answers
2

Assigning CSS with Units


When you set element.style.top and element.style.left, you need to specify units (usually pixels, px, when doing this type of element transformation). In your case, you only set the units in the eventMoveHandler, which makes it only work in the move handler.


In the below snippet I've changed it to automatically add px to repositionElement, and removed the units from eventMoveHandler. I also removed boxWrapper.style.width = w; and boxWrapper.style.height = h; in resize, as they didn't have units, and it was unclear where the boxWrapper dimensions were used.


Reassigning Coordinates


For me, it was easier to think about this problem in terms of the center of the box. Your original code uses the prerotated top left corner to keep track of position, which becomes hard to imagine on the rotated rectangle. The center, on the other hand, is always the center. To use the center, I added/changed this css:


.box {
transform: translate(-50%, -50%);
}
.box-wrapper {
transform-origin: top left; /* changed from `center center` */
}

It also simplifies the code a bit in resizeHandler/eventMoveHandler:


if (xResize) {
if (left) {
newW = initW - wDiff;
} else {
newW = initW + wDiff;
}
newX += 0.5 * wDiff;
}
if (yResize) {
if (top) {
newH = initH - hDiff;
} else {
newH = initH + hDiff;
}
newY += 0.5 * hDiff;
}

Now box-wrapper's style.top and style.left coordinates are actually at the center of the box. If this coordinate system doesn't work, we can revisit it.


Taking Rotation Into Account When Resizing


From here, we need to take into account the rotation of the box when resizing it. For example, when the box is rotated 90 degrees, all of the x changes become y changes. To transform them, you can use Math.cos and Math.sin


var initRadians = initRotate * Math.PI / 180;
var cosFraction = Math.cos(initRadians);
var sinFraction = Math.sin(initRadians);
//...
var wDiff = (event.clientX - mousePressX);
var hDiff = (event.clientY - mousePressY);
var rotatedWDiff = cosFraction * wDiff + sinFraction * hDiff;
var rotatedHDiff = cosFraction * hDiff - sinFraction * wDiff;
//...
if (xResize) {
if (left) {
newW = initW - rotatedWDiff;
} else {
newW = initW + rotatedWDiff;
}
//...
}
if (yResize) {
if (top) {
newH = initH - rotatedHDiff;
} else {
newH = initH + rotatedHDiff;
}
//...
}


Also, when you correct your position, you should be using the sin and cos fractions too, because the position set by style.top and style.left does not normally take rotation into account:


if (xResize) {
//...
newX += 0.5 * rotatedWDiff * cosFraction;
newY += 0.5 * rotatedWDiff * sinFraction;
}
if (yResize) {
//...
newX -= 0.5 * rotatedHDiff * sinFraction;
newY += 0.5 * rotatedHDiff * cosFraction;
}

Applying a Minimum Width and Height


As pointed out in the comments, the behavior is strange when the dragged edge or corner goes beyond the anchored edges. In this case, you'd expect either the box to be flipped, or the box to stop being resized. I'll use a minimum width and height here, since the implementation seems to be simpler.


const minWidth = 40;
const minHeight = 40;
//...
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;
}
}
//..
}
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;
}
}
//...
}

Aside: Removing Event Listeners

Unrelated to the heart of the matter is removing event listeners. Ironically, the code to remove your event listener, leaves up an event listener in mouseup:


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

This isn't that big of a problem, as the function doesn't do too much if run repeatedly, and the closures don't really take up that much memory. But to really clean it up, we can change it to something like:


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

The Result


All together, it looks like:




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(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>




[#2363] Friday, October 30, 2020, 4 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
alyssiat

Total Points: 608
Total Questions: 102
Total Answers: 101

Location: Japan
Member since Sat, Jun 6, 2020
4 Years ago
alyssiat questions
;