DBILITY

div move + rotate + resize test 본문

front-end & ui/javascript

div move + rotate + resize test

DBILITY 2021. 5. 20. 19:00
반응형

수학을 열심히 해야 하는 이유가 이런 것이었다. 삼각함수, 회전 변환..집에 가서 '수학이 필요한 순간'을 읽어야겠다.

우측과 하단(가장 쉬운 형태)으로 크기 변경이 가능하고 이동, 회전도 된다.

<!doctype html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        html, body {
            margin: 0;
            padding: 0;
            height: 100%;
            overflow: hidden;
        }

        .container {
            position: absolute;
            margin: 0;
            width: 100%;
            min-height: 100%;
            box-shadow: 0 0 0 1px #000 inset;
            padding: 0;
        }

        .layer {
            position: absolute;
            width: 200px;
            height: 200px;
            text-align: center;
            padding: 0;
            border: 1px dotted black;
            vertical-align: middle;
            left: calc(50% - 100px);
            top: calc(50% - 100px);
            transform: rotate(0deg);
            padding: 0;
            margin: 0;
        }

        .border-red {
            cursor: all-scroll;
            border-color: red;
        }

        .border-black {
            cursor: default;
            border-color: black;
        }

        .ui-rotate-handle {
            width: 20px;
            height: 20px;
            background-color: transparent;
            border: 0px solid #000000;
            position: absolute;
        }

        .ui-rotate-s {
            bottom: -40px;
            left: calc(50% - 10px);
            cursor: grab;
            background-image: url("./image/arrow-repeat.svg");
            background-repeat: no-repeat;
            background-size: 100%;
        }

        .ui-rotate-dsp-handle {
            width: 33px;
            height: 20px;
            background-color: transparent;
            border: 1px solid darkcyan;
            position: absolute;
        }

        .ui-rotate-dsp-output {
            bottom: -40px;
            left: calc(50% + 20px);
            cursor: auto;
            color: darkcyan;
        }

        .ui-resizable-handle {
            width: 16px;
            height: 16px;
            background-color: transparent;
            border: 0px solid #000000;
            position: absolute;
        }

        .ui-resizable-e {
            right: -8px;
            top: calc(50% - 8px);
            cursor: grab;
            background-image: url("./image/arrows-expand-horizontal.svg");
            background-repeat: no-repeat;
            background-size: 100%;
        }

        .ui-resizable-s {
            bottom: -8px;
            left: calc(50% - 8px);
            cursor: grab;
            background-image: url("./image/arrows-expand-vertical.svg");
            background-repeat: no-repeat;
            background-size: 100%;
        }

        img {
            max-width: 100%;
            max-height: 100%;
            position: absolute;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            margin: auto;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="layer">
        <img src="https://dummyimage.com/200x200/fff/000.png&text=Rotate,Move,Resize!" alt="">
    </div>
</div>
<script>

    const log = function () {
        for (argument of arguments) {
            console.log(argument);
        }
    };

    let eEle, sEle, angleDsp, rotator;
    let resizeOn = false;
    let dragOn = false;
    let rotateOn = false;
    let mouseMoveX = 0, mouseMoveY = 0, mouseDownX = 0, mouseDownY = 0;
    const MIN_WIDTH = 16, MIN_HEIGHT = 16;
    let layers = document.getElementsByClassName("layer");
    let container = document.getElementsByClassName("container")[0];
    rotator = document.createElement("div");
    rotator.classList.add("ui-rotate-handle", "ui-rotate-s");
    rotator.id = "rotator";

    eEle = document.createElement("div");
    sEle = document.createElement("div");

    eEle.classList.add("ui-resizable-handle","ui-resizable-e");
    sEle.classList.add("ui-resizable-handle","ui-resizable-s");

    angleDsp = document.createElement("div");
    angleDsp.classList.add("ui-rotate-dsp-handle", "ui-rotate-dsp-output");
    angleDsp.innerText = "0";

    const fnGetAngle = function (element) {
        let val = {degree: 0, radian: 0};
        let tgt = window.getComputedStyle(element, null);
        let tf = tgt.getPropertyValue("-webkit-transform") ||
            tgt.getPropertyValue("-moz-transform") ||
            tgt.getPropertyValue("-ms-transform") ||
            tgt.getPropertyValue("-o-transform") ||
            tgt.getPropertyValue("transform") ||
            "none";
        if (tf && tf != "none") {
            let matrix = tf.split("(")[1].split(")")[0].split(",");
            let radian = Math.atan2(Number(matrix[1]), Number(matrix[0])); // radian
            let degree = Math.round(radian * 180 / Math.PI);
            degree = degree >= 360 ? 360 - degree : (degree < 0 ? 360 + degree : degree);
            radian = Math.round((degree * Math.PI / 180) * 1000000) / 1000000;
            val.degree = degree;
            val.radian = radian;
        }
        return val;
    };

    const fnGetNewPosition = function (element, newWidth, newHeight) {
        let angle = fnGetAngle(element);
        let pos = {left: element.offsetLeft, top: element.offsetTop};
        let initW = element.clientWidth;
        let initH = element.clientHeight;
        let x = Math.round(-initW / 2);
        let y = Math.round(initH / 2);
        let newX = y * Math.sin(angle.radian) + x * Math.cos(angle.radian);
        let newY = y * Math.cos(angle.radian) - x * Math.sin(angle.radian);
        let diff1 = {left: Math.round(newX - x), top: Math.round(newY - y)};

        let newW = newWidth;
        let newH = newHeight;
        x = Math.round(-newW / 2);
        y = Math.round(newH / 2);
        newX = y * Math.sin(angle.radian) + x * Math.cos(angle.radian);
        newY = y * Math.cos(angle.radian) - x * Math.sin(angle.radian);

        let diff2 = {left: Math.round(newX - x), top: Math.round(newY - y)};
        let offset = {left: diff2.left - diff1.left, top: diff2.top - diff1.top};
        let newPos = {left: pos.left - offset.left, top: pos.top + offset.top};
        return newPos;
    };

    window.addEventListener("load", e => {

        rotator.addEventListener("mousedown", function(e) {
            dragOn = false;
            rotateOn = true;
            resizeOn = false;
            let target = this.parentElement;
            let cx = target.offsetLeft + (target.offsetWidth / 2); //회전대상 원점X
            let cy = target.offsetTop + (target.offsetHeight / 2); //회전대상 원점Y
            e.preventDefault();
            e.stopPropagation();
            container.onmousemove = e => {
                if(!rotateOn) return;
                e.preventDefault();
                if (target != null) {
                    //atan2는 두 점 사이의 상대좌표(x, y)의 절대각을 -π ~ π의 라디안 값으로 반환한다.
                    let radian = Math.atan2(e.pageY - cy, e.pageX - cx);
                    let degree = Math.round(radian * 180 / Math.PI) - 90; // radian to degree
                    target.style.transform = "rotate(" + degree + "deg)"; // 대상 회전
                    this.style.transform = "rotate(" + degree + "deg)";  // 로테이터 회전
                    angleDsp.style.transform = "rotate(" + (360-degree) + "deg)"; // 각도표시영역 역회전
                    angleDsp.innerText = degree >= 360 ? 360 - degree : (degree < 0 ? 360 + degree : degree)+"˚"; // 각도표시
                    //내부에 this로 자신을 참조하고 싶으면 arrow function을 사용하지 말라고 적혀 있음. 위의 this는 rotator
                }
            };

            layers[0].appendChild(angleDsp);
            angleDsp.onmousedown = e => {
                e.preventDefault();
                e.stopPropagation();
                dragOn = false;
                rotateOn = false
                resizeOn = false;
            };
        });

        rotator.addEventListener("mouseup", function(e) {
            rotator.onmousemove = null;
            container.onmousemove = null;
            document.onmousemove = null;
            eEle.onmousemove = null;
            sEle.onmousemove = null;
            rotateOn = false;
        });

        layers[0].addEventListener("mouseover", function(e) {
            this.classList.remove("border-black");
            this.classList.add("border-red");
        });

        layers[0].addEventListener("mouseout", function(e) {
            this.classList.remove("border-red");
            this.classList.add("border-black");
        });

        layers[0].addEventListener("mousedown", function(e) {
            if(layers[0].contains(angleDsp)) {
                layers[0].removeChild(angleDsp);
            }
            dragOn = true;
            rotateOn = false;
            resizeOn = false;
            this.appendChild(rotator);
            e.preventDefault();
            e.stopPropagation();
            mouseMoveX = 0, mouseMoveY = 0;
            mouseDownX = e.clientX;
            mouseDownY = e.clientY;
            let target = this;
            container.onmousemove = function(e) {
                if(resizeOn) return;
                e.preventDefault();
                e.stopPropagation();
                mouseMoveX = mouseDownX - e.clientX;
                mouseMoveY = mouseDownY - e.clientY;
                mouseDownX = e.clientX;
                mouseDownY = e.clientY;
                let targetX = target.offsetLeft - mouseMoveX;
                let targetY = target.offsetTop - mouseMoveY;
                let availX = container.clientWidth - target.clientWidth;
                let availY = container.clientHeight - target.clientHeight;
                let offsetLeft = (targetX >= 0 && targetX <= availX ? targetX : targetX > availX ? availX : 0);
                let offsetTop = (targetY >= 0 && targetY <= availY ? targetY : targetY > availY ? availY : 0);
                target.style.left = offsetLeft + "px";
                target.style.top = offsetTop + "px";
            };

            /* 우측 */
            layers[0].appendChild(eEle);
            eEle.addEventListener("mousedown", function(e) {
                if(layers[0].contains(angleDsp)) {
                    layers[0].removeChild(angleDsp);
                }
                mouseMoveX = 0;
                dragOn = false;
                rotateOn = false
                resizeOn = true;
                e.preventDefault();
                e.stopPropagation();

                let target = this.parentElement;

                container.onmousemove = function(e) {
                    resizeOn = true;
                    e.preventDefault();
                    e.stopPropagation();
                    mouseMoveX = e.clientX - target.offsetLeft;
                    if(mouseMoveX > MIN_WIDTH) {
                        let newPos = fnGetNewPosition(target, mouseMoveX, target.clientHeight);
                        target.style.left = newPos.left + "px";
                        target.style.top = newPos.top + "px";
                        target.style.width = mouseMoveX + "px";
                    } else {
                        resizeOn = false;
                    }
                };
            });

            /* 하단 */
            layers[0].appendChild(sEle);
            sEle.addEventListener("mousedown",function(e) {
                if(layers[0].contains(angleDsp)) {
                    layers[0].removeChild(angleDsp);
                }
                mouseMoveY = 0;
                dragOn = false;
                rotateOn = false
                resizeOn = true;
                e.preventDefault();
                e.stopPropagation();
                let target = this.parentElement;

                container.onmousemove = function(e) {
                    e.preventDefault();
                    e.stopPropagation();
                    mouseMoveY = e.clientY - target.offsetTop;
                    if(mouseMoveY > MIN_HEIGHT) {
                        let newPos = fnGetNewPosition(target, target.clientWidth, mouseMoveY);
                        target.style.left = newPos.left + "px";
                        target.style.top = newPos.top + "px";
                        target.style.height = mouseMoveY + "px";
                    } else {
                        resizeOn = false;
                    }
                };
            });
        });

        document.addEventListener("mouseup", e => {
            e.preventDefault();
            e.stopPropagation();
            rotator.onmousemove = null;
            container.onmousemove = null;
            document.onmousemove = null;
            dragOn = false;
            rotateOn = false;
            resizeOn = false;
            mouseMoveX = 0, mouseMoveY = 0, mouseDownX = 0, mouseDownY = 0;
        });

        container.addEventListener("mousedown", e => {
            e.preventDefault();
            e.stopPropagation();
            let objArray = [rotator, eEle, sEle, angleDsp];
            for (const objArrayElement of objArray) {
                if (layers[0].contains(objArrayElement)) {
                    layers[0].removeChild(objArrayElement);
                }
            }

            dragOn = false;
            rotateOn = false;
            resizeOn = false;
            mouseMoveX = 0, mouseMoveY = 0, mouseDownX = 0, mouseDownY = 0;
        });
    });

</script>
</body>
</html>

반응형

'front-end & ui > javascript' 카테고리의 다른 글

kendogrid auto scroll to row and select  (0) 2021.09.28
javascript currency number format  (0) 2021.08.06
div rotate test  (0) 2021.05.18
div drag test  (0) 2021.05.14
EventSource + Spring Server Sent Event  (0) 2021.05.12
Comments