[HDD] SVG로 지도 구현

mint130·2023년 10월 9일
0

React-HDD

목록 보기
5/5

🤹‍♀️ SVG로 학교 지도 구현하기

SVG를 쓰게 된 계기

홍보 페이지를 계속 어떻게 구현할까 고민 했는데...
어차피 카카오맵 API를 쓰는 김에 이것도 카카오맵으로 처리하고 싶었는데(카카오맵에는 건물들이 다 나와있으니까), 생각만 하던 중에 마땅한 수가 없어서 뒤로 미루고 있었다
어느날 백을 담당중이던 팀장의 제안+인터넷을 찾아보다가
지도를 만들어서 클릭할 수 있게하는 게 어떻겠냐고 해서...

🤔 마땅한 SVG 파일이 없다...
🧏‍♀️ 만들면 된다!

어떻게?

누군가 만들고 간 학교 지도를 레이어로 깔아서 피그마에서 노가다로 도형 만들었다(기존에 지도 깃헙 링크도 있었던 걸로 알고 있는데 폭파되었다..)
사실 별로 오래 안걸림
그리고 피그마에서 만든 파일을 SVG로 변환 시켰다->https://www.svgviewer.dev/svg-to-react-jsx 여기서 jsx로 수정도 가능

🤔 도형을 합쳤다고 생각했는데 코드 상에서는 안 합쳐지고 rect가 엄청 많이 나온다. 한 건물에 rect가 여러개 있다...
🧏‍♀️ Inkspace로 합침

이게 무슨 건물이지?

의도한 바는...건물마다 컴포넌트가 분리되어 건물을 클릭하면 해당 건물 전시회 정보를 불러오는 것 이기 때문에, 컨포넌트를 분리하는 게 중요했다
위에서 Inkspace로 여러 rect를 한 컴포넌트로 합쳤기 때문에, 거기에 id를 붙여서 구분할 수 있게 했다

🤔 스타일을 어떻게 입히는가(styled-component를 사용할 것)
🧏‍♀️ 솔직히 잘 모르겠어서 gpt한테 물어봤다

const Rect = styled.div`
    transition: fill 0.2s;
     &:hover {
        fill: #ff0000; 
    }
`;

...

const Promotion_map = (props) => (
    <svg
        width={815}
        height={528}
        viewBox="0 0 815 528"
        fill="none"
        id="svg40"
        xmlns="http://www.w3.org/2000/svg"
        {...props}
    >
      <rect width={815} height={528} fill="white" id="rect1" />
      <Rect as="rect"
          x={244.17}
          y={27.8761}
          width={66.1104}
          height={38.0104}
          transform="rotate(25.1765 244.17 27.8761)"
          fill="#D9D9D9"
          id="I"
      /> ...

라고 알려줌!
path도 있다고 하니까
as 태그를 써서 <Element as="rect" /> <Element as="path" />
이런식으로 하랜다

🤔 rect는 되는데 path는 안 먹는다
이유:

<Path
 id="R"
 style={{
 fill: "#d9d9d9",
 }}
 d="M -45.218329 11.261575 L -45.21773 53.805969 L -0.00059213439 53.805947 L -0.00039105698 66.0249 C -17.969428 64.461396 -35.082878 62.464904 -46.015792 60.969479 L -43.38038 95.988445 C -32.176966 98.000779 -3.5785063 102.47849 21.183476 104.28837 C 45.946121 106.09902 103.79958 96.530594 129.63157 91.520329 L 118.59714 61.078854 C 111.0107 62.177403 89.098694 65.052709 62.138132 67.764971 C 55.50417 68.432343 47.078295 68.5607 37.790683 68.331433 L 37.789891 53.80636 L 46.763737 53.805655 L 46.763138 11.261261 L 37.790882 11.260864 L 37.790643 -0.00040853996 L 0.00015990644 -0.00082135131 L 0.00039862067 11.260451 L -45.218329 11.261575 z "
          transform="matrix(0.825487 0.564421 -0.581206 0.813756 109.599 332.358)"
      />

이런식으로 style이 원래 적용되어 있기 때문

🧏‍♀️

const Path = styled.path`
    fill: #d9d9d9; // 기본 색상
    transition: fill 0.2s; // 색상 변화를 부드럽게 만들기 위한 트랜지션
    &:hover {
        fill: #ff0000; // 호버 시 변경하고 싶은 색상
    }
...
 <Path
 id="L"
 transform="rotate(24.8205 214.136 128)"
 d="m 214.136,128 h 36 v 17 h -36 z m 12.14671,-52.657299 21.3951,0.609952 -1.89742,66.555257 -21.3951,-0.60995 z m 3.53024,45.231539 41.99541,0.62086 -0.699,47.28073 -41.99541,-0.62086 z" />

style을 지우면 된다

전시정보 불러오기

부모 컴포넌트에서 getPromotionList를 하면 자식 컴포넌트(map)에서 눌린 건물의 정보가 부모 컴포넌트에parameter로 전달되고, 그 parameter에 있는 hallId로 서버에 쿼리를 보내서 반환되는 전시 정보 리스트를 띄워줘야한다

getPromotionList (부모 컴포넌트)

 const getPromotionList= async (hallId)=>{
        try{
            setHallName(hallId); // 어느 건물인지 위에 띄워주기 위해
            const resp=await axios.get('http://localhost:8080/promotion',{
                params:{
                    hall: hallId // hallId가 parameter로 들어감
                },
                headers: headers }
            );
            console.log(resp.data);
            setPromotionList(resp.data);
        }
        catch (e) {
            console.log(e);
        }

    }
 ...
  <Promotion_map onClick={getPromotionList}/>
 ...
}

Promotion_map (자식 컴포넌트)

const Promotion_map = ({onClick}) => {
  const [hallId, setHallId]=useState(null);

  useEffect(()=>{
    onClick(hallId);
  },[hallId])

  const handleClick = (event) => {
    const clickedElementId = event.target.id; 
    // svg 요소(rect, path..)마다 hall이름을 가진 id가 있기 때문에, click되면 clicekdElementId에 id 저장
    setHallId(clickedElementId);
    // hallId에 clickedElementId 저장
  };

삽질 했던 기존코드

 useEffect(()=>{
     },[hallId])
   
   const handleClick = (event) => {
   
   		const clickedElementId=event.target.id;
    	setHallId(clickedElementId);
    	onClick(hallId);
    //  비동기적으로 업데이트 되는 문제->useEffect에서 hallId가 바뀔 때마다 onClick 호출로 해결

🤔 setHallId를 하고 onClickhallId를 넣으니 비동기적으로 업데이트됨
🧏‍♀️ useEffect를 이용해서 hallId가 바뀔 때마다 onClick을 호출하는 것으로 해결

안 예뻐!

🤔 styled-component를 입혀서 hover을 하면 색이 생기는데 정작 클릭이 됐을 때는 색이 없는 게 어색해서, 클릭 되었을 때도 도형에 색이 있었으면 했다
🧏‍♀️
const [clicked, setClicked]=useState(null); 를 만들고
handleClick함수에 setClicked(clickedElementId)를 추가했다
그러면 건물이 눌리면 clicked 변수에 그 건물의 id가 들어가게 된다
이제 스타일을 바꿔줘야하는데 styled component에서 props를 처음 써봐서 애를 좀 먹었다

const Rect = styled.rect`
   fill: ${(props) => (props.isClicked ? '#013B70' : '#ECECEC')};
   
   transition: fill 0.2s;
    &:hover {
       fill: #013B70; 
   }

...
const Promotion_map = ({onClick}) => {
const [hallId, setHallId]=useState(null);
const [clicked, setClicked]=useState(null);

 useEffect(()=>{
   onClick(hallId);
 },[hallId])

 const handleClick = (event) => {
   const clickedElementId = event.target.id;
   setHallId(clickedElementId);
   setClicked(clickedElementId);

 };

return (
     <svg
         viewBox="0 0 815 528"
         fill="none"
         xmlns="http://www.w3.org/2000/svg"
         {...onClick}
         onClick={handleClick}
     >
	<Rect as="rect"
             x={244.17}
             y={27.8761}
             width={66.1104}
             height={38.0104}
             transform="rotate(25.1765 244.17 27.8761)"
             id="I"
             isClicked={clicked === "I"}
/>
       

isClicked props를 만들어주고, clickedid와 같으면 fill 스타일이 실행되게 했다(isClicked가 있으면(clicked가 id라는 뜻이니까==눌렸다) #013B70, 없으면 #D9D9D9)
svg내의 컴포넌트를 클릭하게 되면 clickedElementId에 클릭된 컴포넌트의 id(건물 이름)이 들어가게 된다
hallIdclickedElementId로 바꾼다
hallId가 변경되었으므로 onClick을 호출
onClick를 사용하는 부모컴포넌트의 axios 요청 매개변수 hallId로 서버에 요청
서버가 hallId에 등록된 전시 정보 리스트를 불러옴
Clicked가 현재 눌린 건물의 id로 변경
컴포넌트의 isClicked가 현재 눌린 건물의 id와 일치한다면 true(건물에 색이 생김)

결과물

0개의 댓글