캔버스와 네이버 지도 API를 연동하기 위한 과정
🎯 이 문서를 읽고 난 후의 상태
- 프로젝트와 문제에 대한 컨텍스트를 이해했다.
- 지도와 캔버스를 연동하기 위해 어떤 설계 과정을 거쳤는지 이해했다.
- 네이버 지도와 캔버스를 연동하는 과정 속에서 겪은 어려움과 해결을 위한 시도를 알았다.
- 최종적으로 어떻게 네이버 지도와 캔버스를 연동했는지를 이해했다.
🤔 배경
현재 네이버 부스트캠프 9기에 참여해서 배우고 있다.
어느덧 마지막인 그룹 프로젝트를 하게 되었는데, "위치 기반 서비스"를 주제로 선정해서 진행하게 되었다.
"위치 기반 서비스"라는 큰 카테고리 내에서, 세부 주제를 정해서 진행해야만 했었다.
사업성이나, 아이디어의 독창성보다는 진짜 "우리가 쓸 법한, 우리 주변에 있는 문제를 해결해보자." 라는 취지에서 시작한 주제이다.
주제는 "중장년층을 위한 접근성을 바탕으로 한 위치 기반 서비스"로, 핵심은 지도 위에 캔버스를 띄우고, 거기에 자녀가 경로를 표시해서 부모님에게 전달하는 것이다.
이해를 돕기 위해서 먼저, 우리 프로젝트에 대한 깃허브 링크를 남긴다.
🤔 어떻게 시작할 것인가?
내가 팀에서 맡게 된 일은 지도와 캔버스 사이의 연동이었다.
네이버 지도와 캔버스 모두 처음이었기에, 어떻게 연동을 시킬 지에 대한 고민이 많았다.
그래서 제일 먼저 한 일은 자료조사였다.
일단, 이 기술들이 무엇인지 알고, 어떻게 돌아가는 지에 대해서 알아야 설계를 할 수 있었기 때문이다.

찾은 자료의 일부분인데, 한번 정리하고 남은 자료들이다.
구글링 하면 나오는 온갖 사용 자료와, 네이버 지도 팀의 QnA, 타입 관련해서는 타입 깃허브 등 많은 자료를 찾았던 것 같다.
관련해서 혹시라도 도움이 될까 싶어 몇 가지 참고자료를 남긴다.
- 줌레벨과 관련해서는 네이버 지도는 1~21단게를 사용중이며, 1~6단계는 모두 같은 단계로 취급한다. 추상적으로는 16단계가 맞으나, 엄격히 말하면 21단계이다.
🧑💻 함께 찾은 방향성
해당 자료 조사를 바탕으로 동료들과 함께 회의를 진행했다.
어떤 관점에서 접근해야할 지, 어떤 기능이 필요한 지 등을 함께 논의하였다.
특히, 지도와 캔버스를 각기 다른 레이어로 두고 다루기로 했어서, 레이어 사이에 어떤 인터페이스를 바탕으로 다룰 것인지를 함께 논의하였다.
처음에는 다음과 같은 형태를 생각했었다.
이를 바탕으로 생각하고 있었는데, 동료가 기가막힌 해결책을 주었다.
위도와 경도만을 갖고 데이터를 만들자.
그리고 이를 바탕으로 캔버스에서는 좌표로 바꿔서 출력을 하자는 의미였다.
배경이 되는 지도가 캔버스 위에 그려질 그림들의 기준이 되고 있고, 둘 사이를 연동하려면 어떤 기준점이 필요한데, 그걸 위도와 경도로 하면 어떻겠냐는 의견에서 였다.
처음에는 이해가 잘 되지 않았기에 논의 과정에서 정말 많은 의문과 질문을 던졌다.
단순 말로 표현하자니, 서로 잘못 이해하는 부분도 많았고, 오해하는 부분도 있었다.
이에, 그림을 그려가면서 구조를 논하였고, 이에 생각이 동기화되어 명확하게 이해할 수 있었다.

이렇게 시각적으로 함께 맞추어가다보니, 단순 인터페이스를 넘어서 어떻게 구현을 해야겠다가 보이기 시작했다.
그렇게 뽑아낸 내용은 다음과 같다.
- 지도와 캔버스는 각기 다른 레이어로 두고, 레이어 사이에는 위도와 경도를 주고 받는 인터페이스를 만들자.
- 캔버스에서는 지도의 위도와 경도를 받아서, 캔버스의 좌표로 바꾸어서 출력하자.
- 데이터는 (위도, 경도) 만을 다루자.
- 드래그 이벤트 시에는 각자 동일한 변화값 만큼 움직이면 될 것이다.
- 확대 축소시에는 동일한 비율로 확대 축소가 되면 될 것이다.
이렇게 함께 논의하고, 방향성을 잡았다.
🖼️ 작업 프로세스 추출
방향성이 잡히고, 본격적으로 작업에 착수하게 되었다.
그리고, 제일 먼저 한 일은, 내가 이걸 구현하기 위해서 어떤 과정을 거쳐야하는 지를 파악하는 것이었다.

아직 어떤 기능이 필요한지, 특정한 문제를 해결하기 위해서는 얼마만큼의 시간이 필요한지 견적이 잡히지 않는 상황이었다.
그래서, 기존 개발 경험을 바탕으로 빠르게 예상되는 작업을 나열하고, 순서를 정리하였다. 그리고 그 과정 속에서 필요하다고 생각되는 것들을 적어두었다.
📝 설계에 대한 계획 수립

"길을 먼저 보고 개발에 착수하자." 라는 개발 철학이자 습관이 있다.
완벽하기 보다는 진짜 순수하게 길을 보는 용도로써의 설계. 그렇기에 설계는 최대한 간단하게, 그리고 빠르게 진행할 필요가 있었다.
이 역시도 대략적인 그림을 그리지 않고 설계에 들어갈 경우 시간이 많이 걸릴 위험이 있었다.
그래서 빠르게 무엇에 대해서 고려를 할 지 나열하고, 이에 대해서 순서를 정하였다.
📝 본격적인 설계의 시작 :: 내용 분석하기
설계에 들어가면서, 제일 먼저 한 일은 프로젝트 자체에서의 지도와 캔버스의 좀 더 자세한 구조를 파악하는 것이었다.
이를 파악하기 위해서, 동료들과 많은 이야기를 나누었다. 내가 생각하는 게 맞는지, 동료가 생각하는 바는 무엇인지 등 잦은 상호 동기화 시간을 가졌다.
그렇게 프로젝트에 대해서 서로가 생각하는 것을 일치 시키고, 이 내용을 바탕으로 다음과 같은 그림을 그렸다.

이렇게 작성하면서 관련된 요소의 파악도 진행하였다.

위와 같은 과정을 거쳐서 지도와 캔버스의 구조를 파악하였다.
캔버스의 경우는 노마드 코더님의 캔버스 강의를 필요한 부분만 빠르게 들으면서 파악하고자 했고, 네이버 지도는 예제를 참고하였다.
이를 통해서 각각이 동작하기 위해서는 어떤 데이터가 필요하고, 무엇이 요구되는지를 빠르게 파악할 수 있었다.
이 뿐만 아니라 직접 네이버 API를 뜯어보면서 어떻게 동작하는지 파악도 했는데 이렇게 파악한 내용은 아래와 같다.
⚙️ 캔버스 관련 요소 분석
- 캔버스는 왼쪽 위가 (0, 0) 이며, 오른쪽으로 가면 x가 증가하고, 아래로 가면 y가 증가한다.
- 경도(Longitude)는 캔버스상의 X좌표에 대응된다.
- 위도(Latitude)는 캔버스상의 Y좌표에 대응된다.
- 캔버스의 크기는 고정이 되어 있으며, 그 내부에 그려지는 요소만 변화한다.
- 캔버스와 관련된 이벤트는 캔버스 내부에서만 발생한다.
- 캔버스는 기본 HTML 관련 이벤트가 매핑되어 있으나, 그래픽에 대한 동작은 JS로 구현되어야 한다.
⚙️ 지도 관련 요소 분석
- 지도는 네이버 지도 API를 사용한다.
- 지도는 위도와 경도를 기반으로 한다.
- 각 요소는 타일 형태로 구현이 되어 있다. (타일은 지도의 한 부분을 의미한다.)
- 지도는 캔버스와는 다르게, 지도 자체적으로 이벤트를 가지고 있으며, 이벤트가 발생하면 지도 자체적으로 이벤트 핸들러가 동작한다.
- 지도는
Wrapper가 되는HTML 태그요소보다 약간 큰 사이즈가 랜더링된다. (지도의 크기는 지도의 크기를 의미하며, Wrapper는 지도를 감싸는 HTML 태그를 의미한다.) - 드래그나 줌인 줌아웃 이벤트로 사전에 랜더링 된 범위를 넘어서면 다시 랜더링이 된다.

위와 같이, 지도는 타일 형태로 구성되어 있으며 각 타일은 img 태그로 작성이 되어 있었다.
이벤트나 여타 동작을 처리함에 있어서 기존에 지도가 갖고 있는 이벤트를 덮어 씌우는 방식도 생각해보았으나, 각 이미지 태그를 하나하나 조작하거나, 관련해서 처리하는 로직까지 고려하는 것은 너무 과한 행위라는 생각이 들었다. 또한, 프로젝트의 목표와도 맞지 않는 문제가 있었다.
이에 따라서, 지도와 캔버스를 각기 다른 레이어로 두고, 레이어 사이에는 위도와 경도를 주고 받는 인터페이스를 만들자는 초기 기획 방향으로, 설계를 굳히고, 진행하였다.
📝 지도와 캔버스의 연동 구조 설계
팀의 궁극적인 목표는 모듈화였다.
확장까지 고려했을 때 우리팀이 궁극적으로 지향하는 바는 지도에 연동되는 캔버스를 다른 곳에서도 사용할 수 있도록 하는 것이었다.
지도와는 별개로 지도와 연동되는 로직과, 캔버스를 오픈소스로 만들어서 배포하는 게 목표였다고 볼 수 있다.
이를 위해서는 지도와 나머지 구조간의 의존성을 최대한 끊을 필요가 있었다.

고민 끝에 설계한 구조는 위와 같다.
캔버스와 지도가 연동된 요소를 하나의 컴포넌트로 수립한다.
사용자(다른 개발자)는 캔버스와 지도가 어떻게 연동되었는지 알 필요가 없다.
캔버스에 그림을 그리고, 움직이거나 줌인 줌아웃이 지도와 연동된, 이 기능만 사용할 수 있으면 된다.
그래서, 캔버스와 지도를 하나로 묶어서 <CanvasWithMap>이라는 컴포넌트로 퍼사드 패턴으로 묶었고, 필요한 동작은 외부에서 props나 이벤트 등으로 제공한다.
또한, 지도의 경우도 어디까지나 배경의 역할만을 수행한다. 어떤 지도로 갈아끼워지든 간에, <CanvasWithMap>은 몰라도 되며, 지도에 대해서 동일한 인터페이스로 기능하면 된다.
이에 따라서, 지도와 그 나머지 세부 요소를 전략 패턴으로 갈아끼울 수 있게 설계 하였다.
이를 통해, 지도와 캔버스의 연동 과정에서 각각의 책임을 더욱 명확하게 할 수 있었다.
📝 캔버스와 지도의 좌표 변환 과정 설계
지도와 캔버스에 대해서 이미 본격적인 설계의 시작 :: 내용 분석하기에서 1차적으로 분석한 바가 있다.
좌표 변환은 추가적인 분석을 요구했다.
우선 당장의 완성을 위해서 네이버 지도를 기준으로 삼았기에, 네이버 지도의 좌표 변환을 먼저 생각해보았다.
이와 관련해서 네이버지도 API를 정말 깊게 찾아보았고, 그 내용은 다음과 같다.
위와 같은 발상을 통해서 생각을 했었고, 결과적으로는 좌표 연산은 캔버스에서 매번 진행을 하게 하되, 매핑 과정에 있어서는 재 랜더링을 이용하기로 했다.
이에 대해서는 뒤에서 다시 다루고자 한다.
📝 컴포넌트 z-index 설계

초기에 설명했던 것처럼, 캔버스와 지도는 각각의 레이어로 구성되어 있으며, 각 레이어는 z-index를 통해서 겹치게 된다.
이에 대해서 좀 더 세부적으로 z-index를 어떻게 둘 것인지 설정을 진행하였다.
📝 로딩부터 동작까지 흐름 설계
설계를 하는 과정 속에서 계속 구현 방안을 모색하면서 단순히 연동만 고려할게 아니라 로딩 등도 고려해야 함을 깨달았다.
네이버 지도 객체는 비동기로 받아오는데, 이를 다루는 것은 로딩된 직후에 보여져야 하기에 이런 순서도 고려가 필요했었다.
그래서 앞에서 언급했던 직접 예제를 통해 구현하면서 각 요소가 이렇게 되겠구나를 파악함과 동시에, 멘토님과의 멘토링을 통해서 부족한 점을 보완해서 다음과 같은 설계를 할 수 있었다.

로딩 단계에서는 데이터 처리의 흐름이 고려될 필요가 있어서, 비동기로 진행되더라도, 일련의 흐름을 갖도록 구성했다. (async, await)
세팅의 경우는 로딩된 자료를 바탕으로 지도를 출력하고, 캔버스를 출력한다.
이 과정에서 로딩이 오래 걸릴 경우 로딩 화면을 출력한다.
사용자 동작의 경우는 권한에 따라 다르게 동작하도록 하며, 이는 추후 고려하도록 한다.
📝 이벤트 발생 시 지도와 캔버스 위치를 어떻게 동기화 시킬 지에 대해
네이버 지도의 경우 급격한 위치변화 혹은 줌인 줌아웃 시 로딩이 요구되는 것을 파악했다.
미리 필요한 부분만 다운받아두고, 사용자의 인터렉션에 따라 바로 다음 영역을 받아오는 형식이다.
위 처럼 테스트를 하면서 동작을 파악해보았다.
그리고 이를 바탕으로, 로딩이 진행될 때 꼭짓점의 좌표를 받아서 이때 지도와 캔버스간의 동기화를 이루도록 하면 사용자의 사용성도 개선하고, 미묘하게 지도와 캔버스가 동기화가 안되는 문제가 생기더라도 대응이 될 수 있지 않을까 싶었다.
혹시 몰라 천천히 움직이는 경우도 확인한 결과, 이와 똑같은 로직이 적용되는 것을 발견하였다.
이에 기반해서 생각을 해 보았을 때, 이벤트를 감지해서 수시 연산을 하는 것 보다는 해당 로딩시간에 맞추어서 재연산을 하는 과정도 괜찮은 옵션인 듯 싶었다. 다만 이에 대한 부분은 직접 구현을 하면서 더 적합한 방법으로 진행하고자 했다.
기본적으로 꼭짓점의 위도 경도를 기준으로 좌표 계산하는 것을 핵심으로 두되 다음과 같은 순서를 고려했다.
- 줌인/줌아웃 이벤트 발생 혹은 이동에 대한 이벤트 발생
- 이벤트가 끝난 시점을 기준으로 지도에 변화
- 지도로부터 꼭짓점 좌표 받아오기
- 받아온 좌표를 바탕으로 캔버스 재연산
- 화면에 출력
위의 순서로 이벤트 발생 시 화면과 캔버스를 동기화 시키기로 했다.
📝구현 순서에 대한 설계
마지막으로 구현 순서를 정해서 구현에 들어가기로 했다. 이때 고려한 순서는 아래와 같았다.
- 지도와 캔버스를 겹친 레이어로 출력
- 특정 위치(현재 위치 혹은 임의의 위치 기준)를 중점으로 삼아서 지도 출력
- 네이버 지도의 꼭짓점 좌표를 받아오는 로직 구현
- 캔버스의 꼭짓점과 네이버 지도의 꼭짓점의 위도 경도를 매핑하는 로직 구현
- 위도 경도를 바탕으로 캔버스에 점을 표시할 수 있도록 연산하는 함수 구현
- 연산된 값을 바탕으로 화면에 좌표를 출력하는 로직 구현
- 이벤트가 발생했을 때 4번부터 다시 계산하는 로직 구현
🧑💻 Z-index 설정
설계가 끝나고 제일 먼저 한 일은 z-index 설정이었다.
tailwind.config.js이며, 우선은 기능 테스트를 위해 하드코드 형태로 빠르게 Z-index를 설정할 수 있게 하였다.
🧑💻 캔버스와 지도를 겹쳐서 보여주기
그 다음으로 한 것은 지도를 구현한 것이었다. 아래는 지도의 코드이다.
그리고 레이어의 기본이 될 캔버스를 구현하였다.
캔버스에는 의도적으로 사각형을 배치하였다. 이를 통해서 캔버스가 제대로 보이는지 확인하고자 했다.캔버스의 배경색을 투명하게 만들고 화면을 꽉 채우게 만들어서 지도와 겹쳐보일 수 있게 만들었다.
그리고 위와 같이 캔버스를 구현하고, CanvasWithMap이라는 컨테이너로 지도와 함께 감싸서 보여줄 수 있게 하였다.
🧑💻 기술적 어려움 : 어떻게 겹쳐있는 레이어에 이벤트를 발생시킬 것인가?
앞서서 지도와 캔버스를 겹치는 과정까지는 순조롭게 진행되었다.
그러나, 생각지도 못한 곳에서 큰 어려움이 찾아왔다.
위의 top-child와 bottom-child 처럼 겹쳐있는 레이어에 대해서 어떻게 동시에 같은 이벤트를 발생시킬 것인가에 대한 어려움이 있었다.

우리의 서비스에서 지도는 위와 같이 동작을 해야했다.
지도와 캔버스가 겹쳐져 있을 때, 지도와 캔버스 모두에 이벤트가 발생해야 했다.
그리고, 우리가 처리해야 하는 이벤트는 다음과 같았다.
- 클릭
- 드래그
- 줌인/줌아웃
이 세가지 이벤트였다.
사실 처음에는 대수롭지 않게 아주 간단하게 생각했었다.

당연하게 top-child를 클릭하면 바로 아래 레이어인 bottom-child에게도 그 이벤트가 적용될거라고 생각했기 때문이다.
그러나, 이는 의도대로 동작하지 않았다. top-child에는 정상적으로 이벤트가 적용이 되나, bottom-child에는 적용이 되지 않았기 때문이다.
🤔 원인 분석 : 쌓임 맥락(Stacking Context)
원인은 스택 컨텍스트(Stacking Context)에 의한 문제였다.
스택컨텍스트 자체에 관련해선 에전에 작성한 위의 글을 참고하면 좋다.
CSS에서 요소가 화면에 보여지는 스택 컨텍스트라는게 존재한다. z-index를 걸게 되면, 높은 z-index값을 가진 요소는 낮은 값을 가진 요소보다 앞에 위치하게 된다.
그리고, 브라우저는 클릭 이벤트가 발생했을 때 가장 위에 있는 요소부터 이벤트를 처리한다.
top-child가 위에 있으므로, 여기에 이벤트를 제일 먼저 전달한다. 그리고 top-child가 이 이벤트를 가로채고 해결한 상태로, stopPropgation같은 요소로 이벤트 전달을 막아서 생기는 문제였다.
실제로, 우리가 헤더 등을 absolute로 구현하면 여기에만 이벤트가 적용되고 다른 곳에는 적용이 안되는데.. 이를 간과했던 것이다.
🧑💻 1차 시도 : pointer-event 속성을 끄기
첫 번째 시도로는 위에 있는 요소인 top-chlid의 pointer-event 속성을 끄는 것이었다.
pointer-events - CSS: Cascading Style Sheets | MDN
pointer-event라고 함은 CSS의 속성 중 하나로, 요소가 마우스 클릭, 터치, 커서 이동과 같은 포인터 이벤트를 받을 수 있는지 여부를 제어하는 속성이다.
단순하게 생각해서, 제일 위에 있는 요소가 이벤트를 다 잡아먹고 있다면, 그걸 끄면 되는게 아닌가? 하는 생각에서 였다.
우리는 tailwindcss를 사용하고 있었기에, 다음과 같이 pointer-event-none 클래스를 주는 것만으로도 끌 수 있었다.
이렇게 하면 top-child의 모든 포인터 이벤트를 끌 수 있다.

pointer-event-none을 주게 되면, top-child는 이벤트를 받지 않게 되고, bottom-child는 이벤트를 받게 된다.
그러나, 이는 우리가 원하는 바가 아니었다. 우리는 top-child, bottom-child 모두 같은 이벤트가 동시에 발생하길 원했기 때문이다.
🧑💻 2차 시도 : 자식 요소에 직접적으로 이벤트를 트리거하기
두번째 시도는 cotainer에서 이벤트가 발생하면 이를 top-child와 bottom-child에서 트리거하는 방식이었다.

클릭을 기준으로만 생각을 했으며, 클릭 이벤트가 container에 발생하면 이를 top-child와 bottom-child에게 트리거하는 방식이었다.
이를 위해서 다음과 같이 코드를 구현하였다.
이렇게 하면, container에 클릭 이벤트가 발생하면 top-child와 bottom-child에게 클릭 이벤트를 트리거할 수 있게 된다.
다만, 이는 2가지의 문제점이 있었다.
ref를 통한 방식은HTMLElement의 기본 이벤트만 트리거할 수 있다.click()과 같이 이벤트를 트리거할 경우 이벤트를 발생시킬 수는 있지만, 마우스가 클릭된 위치 등에 대한 부가정보를 전달할 수 없다.
UI 이벤트는 위와 같이 정의되어 있으며, 이벤트 객체에는 마우스 클릭된 위치, 클릭된 버튼 등에 대한 부가정보가 담겨있다.
다만 공식 문서를 살펴보면, 지원하는 Trigger가 많지 않다.
우리는 네이버 지도 및 여러 지도에 대한 연동을 생각해야 했기 때문에, 조금 더 다양한 이벤트를 생성해서 넘겨줄 필요가 있었다.
단순히 다양한 지도에 대응하는 것 뿐 아니라, 네이버 지도 등의 특정 지도만 놓고 보더라도, 커스텀 이벤트를 만들어서 제공하고 있었기 때문이다.
또한, click()과 같은 메서드를 통해서 이벤트를 트리거할 경우, 이벤트 객체를 생성하지 않기 때문에 이벤트 객체에 대한 부가정보를 전달할 수 없다.

위 그림을 보면 알겠지만, 이 방식은 겹쳐있을 때 뿐만 아니라, 멀리 떨어져있는 요소에 대해서도 이벤트를 트리거할 수 있게 해준다.
또한, ref.click()은 인자를 전달받지 않는다.
이 말은 이벤트 객체를 전달할 수 없다는 것이고, 이벤트 발생 시점의 정보를 전달할 수 없다는 의미가 되기도 한다.
실제로 이와 관련해서 출력해보면 다음과 같이 나온다.

위를 보면 clickX, clickY 등 여러가지 정보가 다 0으로 채워져있는 것을 볼 수 있다.

위에서 볼 수 있듯이 인자가 0인 것을 볼 수 있다.
🧑💻 3차 시도(성공) : Custom Event를 통한 이벤트 전달
부스트캠프의 과정 중에서 EventEmiiter와 관련된 내용을 학습했었다.
Node.js 환경에서 멀티 쓰레드를 다루는 방법을 학습했었는데, 이 과정 속에서 EventEmitter를 사용했었다.
이때에 커스텀 이벤트를 발생시키는 방법을 학습했었기에 이게 문뜩 떠올라서 적용해보기로 했다.
방식은 간단하다.
new Event('click')과 같이 이벤트 객체를 생성하고, 이것을 dispatchEvent() 로 잡아주는 방식이다.
이에 대한 구조는 다음과 같다.

기존에 그렸던 것과는 큰 차이가 없으나, 방법은 이벤트를 직접적으로 발생시키기 보다는 커스텀 이벤트를 정의해서 이거에 기반해서 전달하는 것이다.
이렇게 했을 경우의 장점은 다음과 같다.
"click"과 같이 기본 이벤트를 생성해서 전달하게 되면,click()과 똑같은 효과를 유발할 수 있다. 즉, 기본 이벤트 사용이 가능하다.- 네이버지도나, 여러 지도가 만든 커스텀 이벤트를 그대로 사용할 수 있다.
이에 대한 원리는 다음과 같다.
HTML 표준 방식을 사용하기에 이를 사용하기 위해서는 ref가 필요했다.
이미 위에서 ref를 사용했으므로 이를 활용하기로 하였다.
이를 위한 코드는 다음과 같다.
이렇게 하면, MouseEvent를 통해서 이벤트를 생성하고, dispatchEvent를 통해서 이벤트를 발생시킬 수 있게 된다.
실제로 작성했던 코드인데, 이렇게 이벤트 객체를 생성하고 dispatchEvent로 받아주는 형식이다.
참고로 찾아보니, ref.current.click()과 dispatchEvent의 차이점은 다음과 같았다.
| 특성 | ref.current.click() | dispatchEvent() 방식 |
|---|---|---|
| 이벤트 전파 | 없음 (버블링 X) | 버블링 가능 (bubbles: true) |
| React SyntheticEvent 동작 | 아님 | 작동 |
| 기본 클릭 동작 실행 | 실행됨 (<a>의 링크 클릭 등) | 실행되지 않음 |
| 사용 목적 | 간단한 DOM 클릭 트리거 | 커스터마이징된 이벤트 처리 |
🤔 콜스택 에러 발생
잘 동작할 줄 알았으나, 다음과 같은 에러가 발생하였다.

에러 메세지를 해석해보니, 콜스택이 초과해버린.. 그런 이슈였다. (맥을 쓰고, 브라우저에서도 이와 관련해서 최적화가 되면서 굉장히 오랜만에 본 이슈라서 신선했다.)
분명 코드 로직에는 문제가 없는데 무엇때문일까…? 싶어서 살펴보니 다음과 같은 설정이 되어있었다.
이벤트 캡쳐링/버블링 관련해서 bubbles 속성을 true로 설정해놓았었는데, 이게 문제였다.

이렇게까지 하고 나니 이벤트와 관련해서는 원래 의도대로 동시에 발생시킬 수 있었고, 좌표 전달도 할 수 있었다.

위와 같이 이벤트가 정상적으로 동작하는 것을 볼 수 있다.
이제 의도대로 이벤트를 발생시킬 수 있게 되었다.
🧑💻 추가적인 고려사항 : 네이버 지도는 자체적으로 이벤트를 처리한다.
이 예제 처럼, 나는 HTMLElement 단위로 생각을 하고 있었다.
앞에서 네이버 지도 등 여러가지를 고려한다고는 했지만 지도에 제대로 무언가를 입혀서 동작을 해보지는 않았었다.
위 링크를 보면 알겠지만, 네이버 지도는 이동에 대해서는 자체적인 기능을 제공하고 있었다.

앞서 언급했듯이, 개발자 도구를 통해 네이버 지도를 살펴보았을 때, 네이버지도는 하나의 타일 단위로 해서 여러 이미지들을 합쳐서 하나의 화면을 보여주는 방식으로 구현되어 있기에, 이를 동시에 처리하기 위해서 이렇게 구현한게 아닐까 싶었다.

클릭한 옵션에 대한 부분을 보더라도, absolute나 z-index와 같은 속성이 보이며, 하나의 레이어 위에서 동작하는게 아닐까 싶었다.
또한, 네이버 지도는 앞에서 확인했듯이 모든 지도를 로딩하는 게 아니라 필요한 부분만 로딩하는 방식이라.. 이런 부분에 대한 처리때문에라도 자체적으로 이벤트 핸들러 등을 다 구현한게 아닌가 싶었다.
위 예제에서 살펴볼 수 있듯이, 이동에 대한 부분 등은 panTo, panBy 과 같은 요소를 사용하면 될 것 같았다.
🧑💻 마지막 문제 : 캔버스와 지도 간의 미묘한 동기화 차이를 어떻게 할 것인가 ?
3차 시도와, 추가적인 고려사항을 통해서 이제 네이버 지도와 캔버스에 대한 연동을 구현할 수 있게 되었다.
그러나 위와 같이 지도와 캔버스의 이동 등이 제대로 동기화가 되지 않은 것을 볼 수 있었다.
수치를 맞춰서 움직인다고 해도, 위의 영상처럼 지도는 매번 재 랜더링이 이루어지기에 필연적으로 차이가 날 수 밖에 없었다.
잠깐 막막하다가.. 다행히 캔버스도 이동할 시 매번 재랜더링을 해주는 것을 떠올렸고, 이에 따라서 일정 수준 이상 이동하면 지도와 캔버스 모두 동시에 재랜더링이 되는 방식을 채택하였다.
드래그의 경우 이벤트가 너무 잦게 발생해서 쓰로틀링이나 디바운싱을 적용해야 했다.
이에 따라서 최종적으로 작성한 코드는 다음과 같다.
🚀 마무리
정말 긴 글이었던 것 같다.
처음 글을 써야지 마음을 먹었을 때만 해도 이렇게 길어질거라고는 생각을 못했는데.. 정제도 잘 안된거 같고..
지속적으로 퇴고좀 해야겠다.
이번 글에서는 캔버스와 지도를 연동하는 방법을 이벤트의 관점에서 알아보았다.
실제로 프로젝트를 진행했던 과정이며, 짧게 적었지만 과정 하나하나를 디버깅하고 추적하는데 정말 오랜 시간이 걸렸던 것 같다.
이번 경험을 통해서 이벤트 자체에 대한 이해도를 높일 수 있었고, 이를 통해서 다른 이벤트에 대해서도 더 빠르게 해결할 수 있을 것 같다.
쓸데없는 사족을 좀만 덧붙이면.. 어떤 이벤트 상황을 줘도 다룰 수 있을 것 같다는 생각이 들정도로.. 정말 고생을 많이하고 탐구도 많이 했었다.
일부러 내가 겪은 경험을 상세하게 적어서.. 비슷한 문제를 겪은 사람들이 있다면 글의 일부분을 통해서라도 도움이 되었으면 한다.
📚 정리
click()등의 트리거를 통해 발생시키는 방식은 이벤트 객체를 전달하지 못한다.MouseEvent를 통해서 이벤트를 생성하고,dispatchEvent로 이벤트를 발생시키는게 가장 범용적인 방법인 듯 하다.- 동시에 같은 이벤트를 발생시키고, 같은 시각적 효과를 낳는 것은 단순히 이벤트 하나로 되는 문제가 아니다. 마지막 고려사항으로 동기화 차이를 고려했듯이, 상황에 맞춰서 추가적인 요소를 고려해야한다.
- 즉, 은총알은 없다. 문제에 맞게 적절한 해결책을 찾아야한다.
- 이 글이 그런 문제를 찾아나갈 때 도움이 되었으면 좋겠다.
긴글 읽어주셔서 감사합니다. 🙇