在javascript中创建可调整大小/可拖动/旋转的视图 使用单位分配CSS 重新分配坐标调整大小时要考虑轮换应用最小宽度和高度

如何解决在javascript中创建可调整大小/可拖动/旋转的视图 使用单位分配CSS 重新分配坐标调整大小时要考虑轮换应用最小宽度和高度

我一直在尝试用Javascript创建类似的东西:

enter image description here

如您所见,可以拖动,旋转和调整容器的大小。大多数情况下都可以正常工作,但是在旋转容器时调整容器大小会产生奇怪的输出。

我希望这会发生:

enter image description here

相反,我得到了:

enter image description here

这是完整的代码:

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.topelement.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更改。要对其进行转换,可以使用boxMath.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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-