ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프론트엔드 UI개발 02_Draggable Element
    웹개발 2023. 6. 26. 01:34

    UI개발을 진행하다 보면 마우스로 특정 엘리먼트를 이동시킬 때가 있습니다. 언뜻 보기에는 구현이 쉬워 보이지만, 마우스 포인터와 엘리먼트의 스타일이 실시간으로 동기화되어 화면에 반영되어야 구현이 가능한 인터렉션입니다. 프론트엔드 UI개발 두 번째 포스팅에서는 엘리먼트의 이동에 대해서 정리하려고 합니다. 

    document.addEventListener("DOMContentLoaded", function () {
        const container = document.querySelector('.area');
        const box = container.querySelector('.area_box');
    
        const draggable = new Draggable(container, box);
    });

    함수 실행은 이전 Scroll Spy(스크롤 스파이)와 동일하게 진행합니다. HTML구조는 단순히 div.area와 div.area_box 두개 정도로 하겠습니다.

    constructor(container, box) {
        this.box = box;
        this.container = container;
    
        const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect();
        const { width: boxWidth, height: boxHeight } = box.getBoundingClientRect();
    
        this.containerWidth = containerWidth;
        this.containerHeight = containerHeight;
        this.boxWidth = boxWidth;
        this.boxHeight = boxHeight;
    
        this.isDragging = null;
        this.originLeft = null;
        this.originTop = null;
        this.originX = null;
        this.originY = null;
    
        this.setEventListeners();
    }

    스크롤 스파이같은경우 엘리먼트의 위치값을 알기 위해서 offsetTop값을 사용하여 문서상을 기준으로 위치값을 가져왔습니다. 여기서는, 뷰포트 기준으로 넓이와 높이를 가져오기 위해서 getBoundingClientRect()을 사용하였습니다. 해당 메서드에서 추출한 width, height 값은 사용하기 편하게 하기 위해서 네이밍을 바꿔서 저장합니다. 그리고, 현재 마우스가 드래그 상태인지 확인해 주는 isDragging, 박스의 현재 위치값인 originLeft, originTop, 마지막으로 마우스 움직이기 전 좌표값을 저장하는 originX, originY를 정의합니다. setEventListeners()를 실행해서 각각의 이벤트에 대한 콜백 함수를 살펴보도록 하겠습니다.

    setEventListeners() {
        this.box.addEventListener("mousedown", (e) => {
            this.isDragging = true;
            this.originX = e.clientX;
            this.originY = e.clientY;
            this.originLeft = this.box.offsetLeft;
            this.originTop = this.box.offsetTop;
        });
    
        document.addEventListener("mouseup", (e) => {
            this.isDragging = false;
        });
    
        document.addEventListener("mousemove", (e) => {
            if (this.isDragging) {
                const diffX = e.clientX - this.originX;
                const diffY = e.clientY - this.originY;
    
                const endOfXPoint = this.containerWidth - this.boxWidth;
                const endOfYPoint = this.containerHeight - this.boxHeight;
    
                this.box.style.left = `${Math.min(Math.max(0, this.originLeft + diffX), endOfXPoint)}px`;
                this.box.style.top = `${Math.min(Math.max(0, this.originTop + diffY), endOfYPoint)}px`;
            }
        });
    }

    엘리먼트를 이동시킨다는 것은 기본적으로 세 가지 이벤트 변화를 확인해야 합니다. mousedown -> mousemove -> mouseup 단계입니다. 이 세 단계를 거치면 엘리먼트를 기존 위치에서 다른 곳으로 이동시킬 수 있습니다. 또 하나 중요한 원리는, 엘리먼트는 스스로 움직일 수 없다는 것입니다. 엘리먼트가 이동되려면, 해당 엘리먼트의 top, left 값이 지속적으로 변해야 합니다. top, left 값이 이동되는 거리는 마우스 포인터에서 그 값을 가져와서 반영을 하면 됩니다.

    • mousedown이 되었을 때, isDragging 값이 true가 되며, 현재 마우스 포인터의 x, y값을 구하고, box 엘리먼트의 top, left 값을 구합니다.
    • mousemove가 감지될 때, 얼마만큼의 거리로 움직였는지 확인하기 위해서, 현재 마우스 포인터 위치에서 기존 값을 빼주면 됩니다. 사용자에게 보이는 영역을 기준으로, 움직인 후 X 값에서 움직이기 전 X 값을 빼면 마우스 포인터 이동 거리를 계산할 수 있습니다. 
    • 다음으로, 엘리먼트가 움직일 수 있는 전체 넓이를 제한해 줘야 합니다. 그렇지 않으면, 화면 전체로 박스가 움직이는 오류가 생길 수 있기 때문입니다.
    • 해당 값은 전체 컨테이너 넓이 - 박스 넓이, 컨테이너 높이 - 박스 높이를 계산하면 구할 수 있습니다. 즉, 엘리먼트 움직임의 최소/최댓값을 제한해 줘야 정해진 컨테이너 영역 안에서 자연스럽게 박스 엘리먼트가 움직이는 것처럼 보이기 때문입니다.
    • Math.max(0, this.originLeft + diffX)은 우선 최소 영역 좌표를 "0"으로 정의할 수 있습니다. 0이 좌측의 경계선이면, endOfPoint값은 우측의 경계선입니다.

    자바스크립트로 엘리먼트를 드래그하여 이동시키는 UI는 단순해 보이지만, 그 안에 이벤트에 따른 좌표값을 정확하게 반영을 해줘야 구현이 가능합니다.

Designed by Tistory.