resizable한 split 컴포넌트 개발하기

박종대·2022년 11월 3일
0

Convi

목록 보기
9/9

split two children

사내에서 두 컴포넌트를 서로 resizable 할 수 있는 기능을 구현한 적이 있습니다. 당시 React 사용 초창기였기 때문에 어렵게 구현은 했지만 몇 가지 한계점이 있었습니다.

  • children이 2개일 때 한정
  • 방향은 row 방향 한정

주어진 기능을 구현은 했지만 확장성이나 사용성이 거의 없음을 알 수 있습니다. 당시 경험을 떠올리며 현재 개발하려는 convi split panel의 목표를 설정했습니다.

  • multi children
  • props로 방향 설정
  • min/max size 설정
  • initial size 설정

resizer

만약 split할 컴포넌트가 3개가 있다면 resizer는 2개가 필요합니다. 그럼 다음과 같은 공식이 성립합니다.

  • resizer 개수 = split할 컴포넌트 개수 - 1

그림에서 첫번째 resizer는 A와 B의 resize, 두번째 resizer는 B와 C의 resize, 마지막 resizer는 C와 D의 resize를 담당합니다.

resize 로직을 이벤트를 기준으로 3단계로 나눠보면 mousedown on resizer -> mousemove for resize -> mouseup for end of resize 입니다. 해당 단계에 따라 로직을 구현해보겠습니다.

  • mousedown -> mousemove -> mouseup

mousedown

mousemove 이벤트에서 resize를 할 것이기 때문에 mousedown 이벤트에서는 resize를 하기 위해 필요한 정보들을 setting 할 것입니다. 어떤 정보가 필요한지 생각해보겠습니다.

  • resizer 기준 2개의 컴포넌트들의 size들
    - 컴포넌트의 size를 얻어야 겠습니다. 첫 번째 resizer 기준으로 A 컴포넌트와 B 컴포넌트의 DOMRect 객체를 가지고 있는 paneRefs 라는 리스트를 세팅하겠습니다.

    • Element.getBoundingClientRect() 메소드를 통해 DOMRect 객체를 가져올 수 있다.
  • 몇 번째 resizer인지
    - mousedown 이벤트가 발생한 resizer가 몇 번째 resizer인지 알아야 resize할 대상 컴포넌트를 지정할 수 있겠습니다.

  • resizer의 start position
    - mousedown 이벤트에서 start position(client height)을 알고 있으면 추후 move event에서의 position과의 차이가 결국 resizer의 이동 거리(move)가 될 수 있습니다.

    • start position - moved position = move offset

필요한 정보를 세팅 했으면 이제 mousemove 이벤트와 mouseup 이벤트 리스너를 등록해야 합니다.

	document.addEventListener('mousemove', handleMouseMove);
	document.addEventListener('mouseup', handleMouseUp);

mousemove

mousemove 이벤트에서 resize를 진행하겠습니다. 일단 mousemove이벤트 중에 drag 이벤트를 방지하기 위해 preventDefault 메소드를 호출해줍니다.

	moveEvent.preventDefault();
  • resize 될 수 있는 max size와 min size 설정하기
    - resize에 limited가 없으면 mouse가 움직이는 대로 늘어나고 줄어들 것이므로 max size와 min size를 세팅해줍니다. 우리가 split item에서 설정해준 max size, min size는 현재 고려하지 않겠습니다.
    • max size = A size + B size - resizerThickness
    • min size = 0

max size를 구할 때 resizer의 굵기를 반영해야 한다는 점만 주의하면 되겠습니다.

  • move offset 구하기
    -move offset은 mousedown 이벤트에서 구해놓은 startposition과 현재 position의 차이로 구할 수 있습니다.
    • move offset = moveEvent.clientY - startPosition
  • 변경된 size 구하고 state에 반영
    이제 move offset만큼 이동한 사이즈를 구하고 size state에 반영하는 일만 남았습니다.
    • A size += move offset
    • B size -= move offset
    주의할 점은 처음에 구했던 maxSize를 초과하거나 minSize 미만이 될 때의 분기처리입니다.
    이제 size state에 반영하겠습니다.
    		setSizes(newSizes);
    		```

mouseup

이제 등록했던 mousemove 이벤트와 mouseup 이벤트 리스너를 해제하겠습니다.

	document.removeEventListener('mouseup', handleMouseUp);
	document.removeEventListener('mousemove', handleMouseMove);

마치며

결과 컴포넌트는 해당 데모 사이트에서 확인하실 수 있습니다.
Convi Demo Site

작성한 글은 기본적인 뼈대 로직만 설명되어 있고 실제로 구현하실 때는 여러 시행착오를 겪으셔야 할 겁니다. 시행착오라고 해도 event 객체에 어떤 property가 있고 어떤 기능들이 있는지 몰라서 발생하는 문제 정도였습니다.

그리고 split item 마다 min size나 max size를 사용자가 직접 지정해주거나 initial size를 지정하고 싶다거나 부모 컴포넌트 size만큼 size를 grow 시키는 기능 등 많은 기능을 추가해야 쓸만한(?) splitpanel 컴포넌트가 완성될 수 있을 것입니다. Convi에서는 모두 구현되었고 이제 제가 생각하는 SplitPanel 업데이트 버전에서는 hover animation을 넣으려고 합니다.

profile
Frontend Developer

0개의 댓글