如何解决在javascript中创建可调整大小/可拖动/旋转的视图 使用单位分配CSS 重新分配坐标调整大小时要考虑轮换应用最小宽度和高度
我一直在尝试用Javascript创建类似的东西:
如您所见,可以拖动,旋转和调整容器的大小。大多数情况下都可以正常工作,但是在旋转容器时调整容器大小会产生奇怪的输出。
我希望这会发生:
相反,我得到了:
这是完整的代码:
https://jsfiddle.net/c0krownz/
或
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',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',function () {
window.removeEventListener('mousemove',false);
}
rightMid.addEventListener('mousedown',e => resizeHandler(e,false,true,false));
leftMid.addEventListener('mousedown',false));
topMid.addEventListener('mousedown',true));
bottomMid.addEventListener('mousedown',true));
leftTop.addEventListener('mousedown',true));
rightTop.addEventListener('mousedown',true));
rightBottom.addEventListener('mousedown',true));
leftBottom.addEventListener('mousedown',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',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>
解决方法
使用单位分配CSS
设置element.style.top
和element.style.left
时,需要指定单位(进行这种类型的元素转换时通常为像素px
)。在您的情况下,您只能在eventMoveHandler
中设置单位,这使其只能在移动处理程序中使用。
在以下代码段中,我将其更改为将px
自动添加到repositionElement
,并从eventMoveHandler
中删除了单位。我还删除了boxWrapper.style.width = w;
中的boxWrapper.style.height = h;
和resize
,因为它们没有单位,而且不清楚在何处使用boxWrapper
尺寸。
重新分配坐标
对我来说,更容易从盒子的中心考虑这个问题。您的原始代码使用预旋转的左上角来跟踪位置,这在旋转的矩形上很难想象。另一方面,中心始终是中心。要使用中心,我添加/更改了此CSS:
.box {
transform: translate(-50%,-50%);
}
.box-wrapper {
transform-origin: top left; /* changed from `center center` */
}
它还简化了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;
}
现在box-wrapper
的{{1}}和style.top
坐标实际上位于style.left
的中心。如果此坐标系不起作用,我们可以重新访问它。
调整大小时要考虑轮换
从这里开始,我们在调整盒子大小时需要考虑盒子的旋转。例如,当框旋转90度时,所有x更改都变为y更改。要对其进行转换,可以使用box
和Math.cos
Math.sin
此外,在校正位置时,也应该使用sin和cos分数,因为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;
}
//...
}
和style.top
设置的位置通常不会考虑旋转:
style.left
应用最小宽度和高度
正如评论中指出的那样,当拖动的边或角超出锚定边时,行为是奇怪的。在这种情况下,您可能希望该框被翻转,或者该框停止调整大小。我将在此处使用最小宽度和高度,因为实现起来似乎更简单。
if (xResize) {
//...
newX += 0.5 * rotatedWDiff * cosFraction;
newY += 0.5 * rotatedWDiff * sinFraction;
}
if (yResize) {
//...
newX -= 0.5 * rotatedHDiff * sinFraction;
newY += 0.5 * rotatedHDiff * cosFraction;
}
旁边:删除事件监听器
与问题的核心无关的是删除事件侦听器。具有讽刺意味的是,删除事件监听器的代码在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;
}
}
//...
}
中留下了事件监听器:
mouseup
这不是什么大问题,因为如果重复运行该函数并不会执行太多操作,并且闭包实际上并不会占用太多内存。但是要真正清理它,我们可以将其更改为:
window.addEventListener('mouseup',function() {
window.removeEventListener('mousemove',eventMoveHandler,false);
},false);
结果
总的来说,它看起来像:
window.addEventListener('mouseup',function eventEndHandler() {
window.removeEventListener('mousemove',false);
window.removeEventListener('mouseup',eventEndHandler,false);
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',false);
window.addEventListener('mouseup',function eventEndHandler() {
boxWrapper.removeEventListener('mousemove',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',function eventEndHandler() {
window.removeEventListener('mousemove',false);
}
rightMid.addEventListener('mousedown',e => resizeHandler(e,false,true,false));
leftMid.addEventListener('mousedown',false));
topMid.addEventListener('mousedown',true));
bottomMid.addEventListener('mousedown',true));
leftTop.addEventListener('mousedown',true));
rightTop.addEventListener('mousedown',true));
rightBottom.addEventListener('mousedown',true));
leftBottom.addEventListener('mousedown',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',false);
window.addEventListener('mouseup',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;
}
,
虽然了解形状转换的基础知识是一个很好的练习,但是不必幸运地做到这一点,因为这已经是一个已解决的问题。我相信史蒂夫已经在您现有的代码中添加了问题修复程序,但是我建议使用konvajs之类的现有解决方案,而不是重新发明轮子。
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',width: width,height: height,});
var layer = new Konva.Layer();
stage.add(layer);
var rect1 = new Konva.Rect({
x: 60,y: 60,width: 100,height: 90,fill: 'red',name: 'rect',draggable: true,});
layer.add(rect1);
var tr = new Konva.Transformer();
layer.add(tr);
tr.nodes([rect1]);
layer.draw();
#container {
border:1px solid black;
width: 100%;
height: 100%;
}
<script src="https://unpkg.com/konva@7.1.4/konva.min.js"></script>
<h1>Resizable shape</h1>
<div id="container"></div>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。