Tenho tentado criar algo assim em Javascript:
Como você pode ver, o contêiner pode ser arrastado, girado e redimensionado. A maioria das coisas funciona bem, mas o redimensionamento do contêiner quando ele é girado produz uma saída estranha.
Espero que isso aconteça:
Em vez disso, recebo o seguinte:
Aqui está o código completo:
https://jsfiddle.net/c0krownz/
ou,
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>
Ao definir element.style.top
e element.style.left
, é necessário especificar unidades (geralmente pixels px
,, ao fazer este tipo de transformação de elemento). No seu caso, você define apenas as unidades no eventMoveHandler
, o que faz com que funcione apenas no manipulador de movimento.
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.
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.
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;
}
Conforme apontado nos comentários, o comportamento é estranho quando a aresta ou canto arrastado vai além das arestas ancoradas. Nesse caso, você esperaria que a caixa fosse virada ou que parasse de ser redimensionada. Vou usar uma largura e altura mínimas aqui, já que a implementação parece ser mais simples.
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;
}
}
//...
}
À parte: Removendo ouvintes de eventos
Não relacionado ao cerne da questão está a remoção de ouvintes de eventos. Ironicamente, o código para remover seu ouvinte de evento deixa um ouvinte de evento em mouseup
:
window.addEventListener('mouseup', function() {
window.removeEventListener('mousemove', eventMoveHandler, false);
}, false);
Isso não é um grande problema, já que a função não faz muito se for executada repetidamente e os encerramentos não ocupam muita memória. Mas para realmente limpar, podemos alterá-lo para algo como:
window.addEventListener('mouseup', function eventEndHandler() {
window.removeEventListener('mousemove', eventMoveHandler, false);
window.removeEventListener('mouseup', eventEndHandler, false);
}, false);
Todos juntos, parece:
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>
Este artigo é coletado da Internet.
Se houver alguma infração, entre em [email protected] Delete.
deixe-me dizer algumas palavras