[React/Typescript] 네이버 지도 API (4편) - 반응형 커스텀 마커 만들기

bible_k_·2023년 11월 1일
1

네이버 Map API

목록 보기
4/5
post-thumbnail

이번 포스팅에서는 지도 마커 아이콘을 반응형 커스텀하는 방법에 대해서 알아보겠습니다.

MarkerOptions 객체에서 icon 속성을 직접 지정하지 않으면 기본 아이콘이 적용됩니다. 마커 아이콘을 이미지로도 지정할 수 있습니다.
하지만 각 위치 이름을 마커에 렌더링하기 위해 HTML 콘텐츠를 사용해보겠습니다.

1. 뷰포트 관리

화면 너비를 기준으로 마커 아이콘을 동적으로 조정할 것입니다. 이를 위해
viewportWidth state에 화면 너비값을 저장합니다.

초기 값은 window.innerWidth를 사용하여 브라우저의 현재 너비로 지정합니다.

viewport가 resize될 때 상태를 변경해주기 위해 useEffect를 통해 resize 이벤트 핸들러를 등록합니다.

 const [viewportWidth, setViewportWidth] = useState(window.innerWidth);

 useEffect(() => {
    const handleResize = () => {
      setViewportWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);
  1. useEffect 훅을 사용하여 컴포넌트가 렌더링될 때 한 번만 실행되도록 설정합니다. ([] 빈 배열을 전달하면 컴포넌트가 처음 렌더링될 때만 실행됩니다.)
  2. handleResize 함수는 화면 크기 변경 이벤트(resize)가 발생할 때 호출되며, 현재 화면 너비를 viewportWidth 상태에 업데이트합니다.
  3. window.addEventListener('resize', handleResize)는 브라우저 창 크기 변경 이벤트를 감지하고, 이벤트가 발생할 때 handleResize 함수를 실행합니다.
  4. return 블록에서는 useEffect를 통해 등록한 이벤트 리스너를 정리하고 제거합니다. 이는 컴포넌트가 언마운트되거나 다시 렌더링될 때 메모리 누수를 방지하는 데 도움이 됩니다.

1. 마커 생성

마커 생성 전후 과정에 대한 내용은 2편을 참고해주시길 바랍니다.

icon 객체의 Property

  • content <string | HTMLElement>
    마커의 아이콘으로 사용할 HTML 마크업 또는 HTML 요소
  • size: <naver.maps.Size | SizeLiteral>
    마커의 크기
  • anchor: <naver.maps.Point | PointLiteral | naver.maps.Position>
    지도 위에 놓이는 마커의 위치와 일치시킬 아이콘의 기준 위치. 기본값은 (0, 0)

content 속성은 필수이며 이 부분에 HTML Element를 넣어줄 것입니다.

  
let newMarker = new naver.maps.Marker({
  position: new naver.maps.LatLng(lng, lat),
  map,
  title: name,
  clickable: true,
  icon: {
  	 //html element를 반환하는 CustomMapMarker 컴포넌트 할당
     content: CustomMapMarker({ title: name, windowWidth: viewportWidth }),
     //마커의 크기 지정
     size: new naver.maps.Size(38, 58),
     //마커의 기준위치 지정
     anchor: new naver.maps.Point(19, 58),
     },
});

3. CustomMapMarker

CustomMapMarker 컴포넌트는 제목과 화면너비를 props로 받아 HTMLElement를 반환합니다.

const CustomMapMarker = ({ title, windowWidth }: {
  title: string;
  windowWidth: number;
}) => {
  const mobileContentArray = [
    '<div style="margin: 0; display: table; padding: 0.5rem; table-layout: auto; border-radius: 2.3rem; border: 0.2rem solid var(--color--darkgreen); background: white; cursor: pointer; position: relative; z-index: 2">',
    '<div style="display: table-cell; display: inline-block; width: 2.5rem; height: 2.5rem; background-image: url(Images/markerIcon.svg); background-size: cover; background-position: center; background-repeat: no-repeat;"></div>',
    '<span style="position: absolute; border-style: solid; border-width: 1rem 1rem 0 1rem; border-color: #ffffff transparent; display: block; width: 0; z-index: 1; top: 3.1rem; left: 0.75rem;"></span>',
    '<span style="position: absolute; border-style: solid; border-width: 1rem 1rem 0 1rem; border-color: var(--color--darkgreen) transparent; display: block; width: 0; z-index: 0; top: 3.35rem; left: 0.75rem;"></span>',
    '</div>',
  ];
  const PCContentArray = [
    '<div style="margin: 0; display: table; padding: 0.5rem; table-layout: auto; border-radius: 2.3rem; border: 0.2rem solid var(--color--darkgreen); background: white; cursor: pointer; position: relative; z-index: 2">',
    '<div style="display: table-cell; display: inline-block; width: 4rem; height: 4rem; background-image: url(Images/markerIcon.svg); background-size: cover; background-position: center; background-repeat: no-repeat;"></div>',
    '<div style="max-width: 23rem; height: 4rem; padding: 0 0.8rem 0 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: table-cell; vertical-align: middle; cursor: pointer; font-size: 1.5rem; letter-spacing: -0.04rem; font-weight: 600; line-height: 4rem;">',
    title,
    '</div>',
    '<span style="position: absolute; border-style: solid; border-width: 1.2rem 1rem 0 1rem; border-color: #ffffff transparent; display: block; width: 0; z-index: 1; top: 4.8rem; left: 1.4rem;"></span>',
    '<span style="position: absolute; border-style: solid; border-width: 1.2rem 1rem 0 1rem; border-color: var(--color--darkgreen) transparent; display: block; width: 0; z-index: 0; top: 5.05rem; left: 1.4rem;"></span>',
    '</div>',
  ];
  if (windowWidth < 768) return mobileContentArray.join('');

  return PCContentArray.join('');
};

export default CustomMapMarker;
  • const mobileContentArray = [ ... ];
    const PCContentArray = [ ... ];
    각 배열 요소는 HTML 문자열로 마커의 내용을 구성합니다. 빈 화면에서 원하는 마크업을 그린 후 HTML문자열로 변환하고 쪼개는 것을 추천드립니다.
  • PC용 아이콘에는 title 프로퍼티를 사용합니다.
  • if (windowWidth < 768) return mobileContentArray.join('');
    화면 너비가 768px 미만인 경우, 모바일 스타일의 마커 내용을 반환합니다.
  • return PCContentArray.join('');
    화면 너비가 768px 이상인 경우, PC 스타일의 마커 내용을 반환합니다.

결과물

⬇️ 싸커퀵 ⬇️
https://soccerquick.kr/

profile
후론트엔드 개발자

0개의 댓글