Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발블로그

기본은 중요했다. 본문

기본은 중요했다.

학교옆메추리 2020. 7. 26. 22:58

아는 만큼 보인다.

이번 우아한테크캠프 3기 두번째 프로젝트의 요구사항 중 하나인 Drag & Drop을 진행하면서 가장 많이 느꼈던 말입니다. 실력이 출중한 팀원의 도움을 받아 구현을 성공한, 제 이야기를 하며 하나씩 설명드리고자 합니다.

 

Drag & Drop  Requirements

먼저 drag & drop의 요구사항을 먼저 설명드리죠.

카드를 누르면, 마우스를 따라다니는 반투명한 카드가 하나 생기며[ 1 ], 기존에 카드는 반투명해집니다[ 2 ].

2번 카드는 현재 drag중인 1번 카드를 drop했을 때 놓일 위치를 간접적으로 보여줍니다. 따라서 마우스 위치가 변함에 따라 변화합니다.

 

같은 컬럼 뿐 아니라, 다른 컬럼으로도 카드를 옮길 수 있습니다. 

 

모르기에 보이지 않는 길

dnd를 구현하는 방법에는 정말 여러가지가 있을 텐데요. 저는 마우스 커서 위치를 이용해서 해결해보려 했습니다.

 

첫 번째 고개

첫 시작으로 카드를 누르면 마우스가 움직임에 따라 반투명한 카드가 따라 움직이는 부분입니다. 같은 엘리먼트를 생성하여 opacity를 조절하면 되겠군요!

 

=> element를 다시 만들 필요 없이  node.cloneNode([deep]);  함수를 사용하면 아주 간단하게 해결할 수 있습니다.

 

MDN cloneNode: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode

 

두 번째 고개

자 이제 마우스의 움직임을 포착해서....! 가 안됩니다. 마우스 바로밑에 element가 따라붙어 움직이니까 이벤트가 제 생각처럼 동작하지 않아요.

 

=> 마우스를 따라 움직이는 반투명 element에 css 속성인  pointer-events: none;  을 추가하면 해당 element는 마우스에 대한 이벤트를 무시하기 때문에 원하는 대로 마우스 이동 이벤트를 핸들링 할 수 있습니다!

 

여담으로 프로젝트가 끝난 후 코드리뷰 시간에 이 문제를 해결하기 위해 해당 element에  display: none;  display: block;  속성을 빠르게 바꾸는 방법으로 하위 엘리먼트에 포인터 이벤트가 전해지도록 하셨던 분이 계시는데, 이 마법의 키워드를 아셨다면 고생을 더셨을 수 있었겠습니다.

 

MDN pointer-events: https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events

 

세 번째 고개

마우스 위치를 얻었습니다. 어떤 element가 마우스 아래에 있는지 확인하기 위해서.... 좌표계산을 무진장 했습니다. getBoundingClientRect로 element의 위치를 알아내서 커서가 그 위치에 있는지 알아내는 방법도 해보고... 참 어렵습니다.

 

=>  document.getElementFromPoint(x, y);  를 사용하면 인자로 넘긴 x, y 좌표상에 있는 element를 찾아줍니다.

 

MDN getElementFromPoint: https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/elementFromPoint

 

네 번째 고개

해치웠나? 네 아닙니다. 아쉽게도 위 함수를 통해 잡힌 좌표가 꼭 카드란 법은 없습니다. 컬럼일 수도 있고, 카드 속 내용, x버튼일 수도 있습니다. 카드 속의 어떤 것(ex. 제목)이 마우스 아래에 있다는 것은 현재 커서가 카드 위에 있다는 뜻입니다. 이를 잡기 위해 if문의 조건에 카드이거나, 카드속 제목이거나, 카드속 내용이거나, 카드속 버튼이거나, 카드..... 하나하나 하기도 힘들 뿐더러 추후에 뷰에 변동이 생긴다면 무슨 문제가 생길지는 아무도 모릅니다!

 

=>  element.closest(selectors);  를 사용하면 DOM 트리의 하위요소로부터 상위 요소로 가면서 element을 탐색할 수 있습니다. 커서 아래 있다고 반환된 객체에 closet('.card')를 사용하면 카드 내부의 어떤 것이든지 card element를 반환하게 될 것입니다. 카드가 아니라면 null이 나올겁니다.

쉽게 설명해서 우리가 자주 사용하는  document.querySelector(selectors);  의 정 반대라고 보시면 됩니다. 

 

MDN closest: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

 

다섯 번째 고개

어찌저찌 구현을 했다고 가정하고, dnd의 기본이 얼추 완성되었습니다. 이제 움직임에 따라 주변 카드들에 변화를 주어 예쁘게 만들어 봅시다. 카드를 같은 컬럼내에서 이동할 때, 우리가 집은 카드가 내려가면 밑에 있던 카드는 올라와야하고, 올라가면 위에 있던 카드는 내려와야 합니다. 순서를 어찌저찌 잘 계산해서 element를 교체합시다. 뭔가 이동은하는데 화면이 너무 정적으로 움직일 뿐더러, 카드를 이리저리 휙휙 움직이면 껌벅껌벅 버벅임 현상이 보일 겁니다.

 

=> 구현이 좀 더 어려울 수 있지만, css의   will-change  속성과  transform 을 활용하여 element들을 자꾸 교체하지 말고 이동시켜 봅시다. 성능상의 이점은 물론 하드웨어 가속(GPU)의 혜택을 받을 수 있기 때문에 우리의 애니메이션은 더이상 버벅이지 않을 것입니다.

 

css 하드웨어 가속: https://web-atelier.tistory.com/39

 

여섯 번째 고개

css의  transition  속성을 이용하여 움직임을 부드럽게 했습니다. 이제 잘 되는 것 같아 보이는....데! 해치웠을 리 없죠. 카드가 딱딱 움직일 때와는 달리 시간을 두고 미끄러져 이동하다가 마우스에 닿으면서 카드들이 와리가리 가끔씩 춤을 춥니다. 움직일때 클래스를 추가해서 위에서 배운  pointer-events: none;  을 적용할 수 있을 것입니다. 트랜지션이 종료되면 다시 클래스를 제거해 주어야겠죠. 어떻게 알아낼지 몰라서 setTimeout으로 transition을 주었던 시간만큼이 지나면 제거하도록 하였습니다.

 

=> 지금 당장은 문제가 없겠으나 시간을 수정할 때마다 신경써주어야 할 부분(종속된 부분)이 늘어나고, 문제가 생길 것만 같은 코드입니다. (정확히 어떤 문제가 생길진 모르겠습니다)  transitionstart, transitionend  이벤트를 활용하면 해당 엘리먼트의 트랜지션이 종료된 시점에 실행될 콜백함수를 지정할 수 있습니다.

 

MDN transition events: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/transitionend_event

느낀 점

웹 프론트앤드 개발자로 근무하면서 2년간 리액트를 사용하여 프로젝트를 진행하였었습니다. 당연히 기초에 대해서도 어느정도 공부는 하였고 (초기에) 중간부터는 필요할 때 대충 구글링해서 찾아보고 복붙하고, npm에서 다운로드하고... 그랬던 것 같습니다.

 

dnd를 구현하며 가장 많이 와닿았던 것은 기본의 부족함이였습니다. 왜 기본이 중요한지를 뼈저리게 깨달았습니다. 위의 예시들처럼 미리 알았다면 고민하지 않았을 문제들이 너무 많았습니다. 고민은 물론 좋지만 2주간의 짧은 시간 내에 요구사항을 만족하는 프로젝트를 완성하기 위해서라면 마냥 좋다고 할 수 없을 것 같습니다.

 

MDN을 틈틈히 읽어봐야겠다는 생각이 많이 듭니다. 기본에 조금 더 충실해졌을 때, 저는 훨씬 더 성장해 있을 것이라고 자신합니다.

 

감사합니다 :)