[HTML][TypeScript] 드래그 앤 드롭 API로 요소 정렬

48366 단어 typescript

소개



이번에는 드래그 앤 드롭 API로 DOM 요소를 정렬해 보았습니다.



패키지.json




{
    "dependencies": {
        "autoprefixer": "^10.2.4",
        "postcss": "^8.2.6",
        "postcss-cli": "^8.3.1",
        "ts-loader": "^8.0.17",
        "tsc": "^1.20150623.0",
        "typescript": "^4.2.2",
        "webpack": "^5.24.3",
        "webpack-cli": "^4.5.0"
    }
}


기본 프로젝트



홈.html




<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Hello</title>
        <meta charset="utf-8">
        <link rel="stylesheet" href="/css/home.page.css">
    </head>
    <body>
        <div class="draggable_area" id="draggable_area_1">
            <div class="draggable_item" draggable="true" id="draggable_element_1">
                <div>
                    title
                </div>
                <div>
                    content
                </div>
            </div>
            <div class="draggable_item" draggable="true" id="draggable_element_2">
                <div>
                    title2
                </div>
                <div>
                    content2
                </div>
            </div>
        </div>
        <script src="/js/homePage.js"></script>
    </body>
</html>


홈페이지.페이지.css




.draggable_area {
    border: 1px solid black;
    background-color: cadetblue;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    height: 20vh;
    width: 40vw;
}
.draggable_item {
    border: 1px solid black;
    background-color: coral;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-around;
    width: 30%;
    height: 90%;
}
.dragging {
    opacity: 50%;
}
.drop_over {
    background-color: cornflowerblue;
}


끌어서 놓기 구현



견인



대상 요소를 드래그할 수 있도록 하려면 "드래그 가능"특성을 추가하기만 하면 됩니다.

<div draggable="true">
</div>


하지만 아무데도 떨어뜨릴 수 없기 때문에 거의 아무것도 할 수 없습니다.

하락



현재 웹 브라우저에서 사용할 수 있는 "droppable"속성이 없기 때문에 직접 구현해야 합니다.

"draggable"속성과 관련된 일부 DOM 이벤트를 사용할 수 있으므로 어떤 요소가 드래그되었고 어디에 놓였는지 알 수 있습니다.

home.page.ts




const parentElement: HTMLElement = document.getElementById('draggable_area_1') as HTMLElement;
function init() {
    for(let i = 0; i < parentElement.children.length; i++) {
        const targetElement = parentElement.children[i] as HTMLElement;
        if(targetElement.classList != null &&
            targetElement.classList.contains('draggable_item'))
        {
            targetElement.ondragstart = ev => handleStartDraggingEvent(ev);
            targetElement.ondragover = ev => handleDraggingOverEvent(ev);
            targetElement.ondrop = ev => handleDroppingEvent(ev);
        }
    }
}
function handleStartDraggingEvent(ev: DragEvent) {
    const target = ev.target as HTMLElement;
    console.log("[DRAG] " + target.id);
}
function handleDraggingOverEvent(ev: DragEvent) {
    // Elements prevent Drop operation by default    
    ev.preventDefault();
}
function handleDroppingEvent(ev: DragEvent) {
    const target = ev.target as HTMLElement;
    console.log("[DROP]" + target.id);
    ev.preventDefault();
}
init();


이제 "handleStartDraggingEvent()"에서 어떤 요소가 드래그되었는지, "handleDroppingEvent()"에서 어떤 요소가 드롭되었는지 알 수 있습니다.
그러나 그들의 정보는 연결되어 있지 않습니다.

그래서 "handleStartDraggingEvent()"에 데이터를 설정하고 "handleDroppingEvent()"에 데이터를 받습니다.

home.page.ts




...
function handleStartDraggingEvent(ev: DragEvent) {
    const target = ev.target as HTMLElement;
    console.log("[DRAG] " + target.id);

    var dataTransfer = ev.dataTransfer;
    if(dataTransfer == null) {
        return;
    }
    dataTransfer.setData("text/plain", target.id);
}
...
function handleDroppingEvent(ev: DragEvent) {
    const target = ev.target as HTMLElement;
    console.log("[DROP]" + target.id);
    const dragTargetId = ev.dataTransfer!.getData("text/plain");
    ev.preventDefault();
}
...


장식 추가 및 요소 정렬



장식 추가


  • 요소를 끌 때 투명도를 추가합니다
  • .
  • 요소를 드래그할 때 색상 변경
  • 요소가 자체인 경우 색상 변경을 방지합니다
  • .

    home.page.ts




    ...
    function init() {
        for(let i = 0; i < parentElement.children.length; i++) {
            const targetElement = parentElement.children[i] as HTMLElement;
            if(targetElement.classList != null &&
                targetElement.classList.contains('draggable_item'))
            {
                targetElement.ondragstart = ev => handleStartDraggingEvent(ev);
                targetElement.ondragover = ev => handleDraggingOverEvent(ev);
                targetElement.ondragenter = ev => handleDraggingEnterEvent(ev);
                targetElement.ondragexit = ev => handleDraggingExitEvent(ev);
                targetElement.ondrop = ev => handleDroppingEvent(ev);
                targetElement.ondragend = ev => handleDropEndEvent(ev);
            }
        }
    }
    ...
    function handleStartDraggingEvent(ev: DragEvent) {
        const target = ev.target as HTMLElement;
        console.log("[DRAG] " + target.id);
        target.classList.add('dragging');
    
        var dataTransfer = ev.dataTransfer;
        if(dataTransfer == null) {
            return;
        }
        dataTransfer.setData("text/plain", target.id);
    }
    function handleDraggingEnterEvent(ev: DragEvent) {
        const target = ev.target as HTMLElement;
        if(target.id === ev.dataTransfer!.getData("text/plain")) {
            return;
        }
        if(target?.classList == null) {
            return;
        }
        target.classList.add('drop_over');
    }
    function handleDraggingExitEvent(ev: Event) {
        const target = ev.target as HTMLElement;
        if(target?.classList == null) {
            return;
        }
        target.classList.remove('drop_over');
    }
    ...
    function handleDropEndEvent(ev: Event) {
        const target = ev.target as HTMLElement;
        if(target?.classList == null) {
            return;
        }
        target.classList.remove('dragging');
        if(target.classList.contains('drop_over')){
            target.classList.remove('drop_over');
        }
    }
    function handleDroppingEvent(ev: DragEvent) {
        const target = ev.target as HTMLElement;
        if(target.classList.contains('drop_over')){
            target.classList.remove('drop_over');
        }
    ...
    }
    


    한 가지 중요한 점은 "ondragenter"와 같은 이벤트가 이 샘플에서 "draggable_item"클래스가 있는 "div"요소뿐만 아니라 해당 하위(텍스트 요소)에 의해 실행된다는 것입니다.
    그리고 "classList"속성이 없습니다.

    따라서 요소에 클래스를 추가하거나 제거할 때 대상에 "classList"속성이 있는지 확인해야 합니다.

    요소 정렬



    요소를 정렬하는 특별한 방법이 없기 때문에 목록에 요소를 추가하고 정렬할 때 parentElement의 자식 요소를 정리하고 정렬된 목록을 추가합니다.

    전체 TS 코드



    home.page.ts




    type DragTarget = {
        id: string,
        element: HTMLElement,
        index: number,
    };
    const parentElement: HTMLElement = document.getElementById('draggable_area_1') as HTMLElement;
    let targets = new Array<DragTarget>();
    function init() {
        for(let i = 0; i < parentElement.children.length; i++) {
            const targetElement = parentElement.children[i] as HTMLElement;
            if(targetElement.classList != null &&
                targetElement.classList.contains('draggable_item'))
            {
                targetElement.ondragstart = ev => handleStartDraggingEvent(ev);
                targetElement.ondragover = ev => handleDraggingOverEvent(ev);
                targetElement.ondragenter = ev => handleDraggingEnterEvent(ev);
                targetElement.ondragexit = ev => handleDraggingExitEvent(ev);
                targetElement.ondrop = ev => handleDroppingEvent(ev);
                targetElement.ondragend = ev => handleDropEndEvent(ev);
    
                targets.push({
                    id: targetElement.id,
                    element: targetElement,
                    index: i,
                });
            }
        }
    }
    function handleStartDraggingEvent(ev: DragEvent) {
        const target = ev.target as HTMLElement;
        console.log("[DRAG] " + target.id);
        target.classList.add('dragging');
    
        var dataTransfer = ev.dataTransfer;
        if(dataTransfer == null) {
            console.error('dataTransfer was null');
            return;
        }
        dataTransfer.setData("text/plain", target.id);
    }
    function handleDraggingEnterEvent(ev: DragEvent) {
        const target = ev.target as HTMLElement;
        if(target.id === ev.dataTransfer!.getData("text/plain")) {
            return;
        }
        if(target?.classList == null) {
            console.log(target);
            return;
        }
        target.classList.add('drop_over');
    }
    
    function handleDraggingExitEvent(ev: Event) {
        const target = ev.target as HTMLElement;
        if(target?.classList == null) {
            return;
        }
        target.classList.remove('drop_over');
    }
    function handleDraggingOverEvent(ev: DragEvent) {
        ev.preventDefault();
    }
    function handleDropEndEvent(ev: Event) {
        const target = ev.target as HTMLElement;
        if(target?.classList == null) {
            return;
        }
        target.classList.remove('dragging');
        if(target.classList.contains('drop_over')){
            target.classList.remove('drop_over');
        }
    }
    function handleDroppingEvent(ev: DragEvent) {
        const target = ev.target as HTMLElement;
        if(target.classList.contains('drop_over')){
            target.classList.remove('drop_over');
        }
    
        console.log("[DROP]" + target.id);
        const dragTargetId = ev.dataTransfer!.getData("text/plain");
    
        const dropped = targets.find(t => t.id === target.id);
        const dragged = targets.find(t => t.id == dragTargetId);
        if(dropped == null ||
            dragged == null) {
            return;
        }
        const droppedIndex = dropped.index;
        dropped.index = dragged.index;
        dragged.index = droppedIndex;
        updateElements();
        ev.preventDefault();
    }
    function updateElements() {
        for(let i = parentElement.children.length - 1; i >= 0; i--) {
            parentElement.removeChild(parentElement.children[i]);
        }
        for(const target of targets.sort((a, b) => a.index - b.index)) {
            parentElement.appendChild(target.element);
        }
    }
    init();
    


    자원


  • HTML Drag and Drop API - Web APIs | MDN
  • draggable - HTML: HyperText Markup Language | MDN
  • Drag Operations - Web APIs | MDN
  • 좋은 웹페이지 즐겨찾기