카카오 지도 api를 이용해서 키워드 검색 후 마커 이동하기 구현

적자생존·2022년 7월 8일
12

teamprojectReview

목록 보기
16/18

1. 구현하고자 하는 기능

https://apis.map.kakao.com/web/sample/keywordList/

키워드로 검색 후 해당 키워드 클릭 시 마커의 위치를 이동하는 기능을 구현해 보고자 한다.

카카오지도의 경우 팀 프로젝트의 핵심이였고 무조건 구현해야 했던 기능이였다.

2. 카카오지도 구현하기

카카오 지도 불러오는 방법은 넘어가고 내부의 코드만 보기로 한다.

가. 지도 불러오기

// 마커를 담을 배열
let markers: any[] = [];

const container = document.getElementById("map");
const options = {
	center: new window.kakao.maps.LatLng(38.2313466, 128.2139293),
	level: 1,
};
// 지도를 생성한다.
const map = new window.kakao.maps.Map(container, options);

containeroptions를 이용해 window.kakao.maps에 지도를 추가하여 생성한다.

나. marker를 생성한다.

const markerPosition = new window.kakao.maps.LatLng(
		38.2313466,
		128.2139293
);
const marker = new window.kakao.maps.Marker({
		position: markerPosition,
});
marker.setMap(map);

1) markerPosition이라는 변수에 마커의 위치를 담습니다.
2) marker라는 변수를 이용해서 위치를 markerPosition의 위치로 지정한다음
3) marker.setMap(map)이라는 메서드를 통해서 map에 생성합니다.

다. 검색 객체와 검색 결과 목록이나 마커를 클릭시 장소명을 표출할 인포 윈도우 생성

const ps = new window.kakao.maps.services.Places();
const infowindow = new window.kakao.maps.InfoWindow({ zIndex: 1 });

1) ps를 이용해 장소 검색 객체를 담습니다. 카카오 자체에서 제공되는 메서드를 이용합니다.
2) infowindow라는 변수에 InfoWindow를 만들어 줍니다.

라. 키워드로 장소를 검색합니다.


// kakao 제공
searchPlaces();

// 내가 적용한 것
const searchForm = document.getElementById("submit_btn");
		searchForm?.addEventListener("click", function (e) {
		e.preventDefault();
		searchPlaces();
});

기존의 searchPlaces()의 html코드는 다음과 같다.

onsubmit함수에 searchPlaces()를 담아서 사용하고 button의 type에는 submit으로 준 것이다.

내가 구현한 html은 다음과 같다

1) button에 직접 id를 주고 그 button의 type을 submit으로 해주었다.

2) searchForm이라는 변수에 submit_btn이라는 아이디를 가진 태그를 집어넣어줬다. 여기선 button이 되고 buttonsearchForm의 변수에 저장되어있다.

3) searchFormaddEventListner를 이용해서 click이벤트를 달아주고 그 이벤트에 searchPlaces()를 달아주었다.

마. 키워드 검색을 요청하는 함수를 달아준다.

function searchPlaces() {
	const keyword = document.getElementById("keyword").value;
  
	if (!keyword.replace(/^\s+|\s+$/g, "")) {
alert("키워드를 입력해주세요!");
  
	return false;
}
  
ps.keywordSearch(keyword, placesSearchCB);
}

1) 에서 만든 searchPlaces()이다.
2) keyword라는 변수에 tag의 아이디가 keyword의 값을 할당해준다.

여기서 id keywordinput이다.

3) 키워드에 아무것도 입력이 되지 않을 시 혹은 정규식에 의해서 올바른 값이 입력되지 않으면 함수가 실행이 되지 않는다.

4) ps.keywordSearch(keyword, placesSearchCB)
올바른 키워드가 입력이 되었으면 에서 만든 ps(장소검색 객체)를 통해 keyword로 장소검색을 요청한다.

바. 장소검색이 완료가 되면?

 function placesSearchCB(data, status, pagination) {
          if (status === window.kakao.maps.services.Status.OK) {
            displayPlaces(data);

            displayPagination(pagination);

            const bounds = new window.kakao.maps.LatLngBounds();
            for (let i = 0; i < data.length; i++) {
              displayMarker(data[i]);
              bounds.extend(new window.kakao.maps.LatLng(data[i].y, data[i].x));
            }

            map.setBounds(bounds);
          } else if (status === window.kakao.maps.services.Status.ZERO_RESULT) {
            alert("검색 결과가 존재하지 않습니다.");
          } else if (status === window.kakao.maps.services.Status.ERROR) {
            alert("검색 결과 중 오류가 발생했습니다.");
          }
        }

1) 장소 검색이 완료되었으면 datadisplayPlaces()의 인자paginationdisplayPagination()로 넣어준다.

1-1) displayPlaces()는 검색 결과 목록과 마커를 표출하는 함수이고,
1-2) displayPagination()는 검색 결과 목록 하단에 페이지번호를 표시하는 함수이며 아래에서 설명함.

2) 카카오 자체 메서드인 LatLngBounds()를 이용해 영역을 지정해 bounds라는 변수에 할당해준다.
검색된 장소 위치를 기준으로 지도 범위를 재설정 하기 위함입니다.

3) map.setBounds(bounds)를 이용해서 검색된 장소 위치를 기준으로 지도 범위를 재설정 합니다.

4) else if문을 통해서 예외처리를 해준다.

사. 검색 결과 목록과 마커를 표시하는 함수를 만든다.

function displayMarker(place) {
          const marker = new window.kakao.maps.Marker({
            map,
            position: new window.kakao.maps.LatLng(place.y, place.x),
          });
          window.kakao.maps.event.addListener(marker, "click", function (mouseEvent) {
              props.setAddress(place);
              infowindow.setContent(`
              <span>
              ${place.place_name}
              </span>
              `);
              infowindow.open(map, marker);
              const moveLatLon = new window.kakao.maps.LatLng(place.y, place.x);
              map.panTo(moveLatLon);
            }
          );
        }

1) 마커를 생성해서 표시해주는 함수입니다.

2) 또한 생성된 마커에 addListener를 이용해 클릭이벤트를 달아주었습니다.

3) 클릭이벤트 발생시 infowindow를 생성하여 보여주고 마커의 좌표를 지도 정 중앙에 위치시킵니다.
map.panTo()라는 메서드를 이용해서 지도를 부드럽게 이동시킵니다.

아. 검색 결과 목록과 마커를 표출하는 함수를 만들어 줍니다.

function displayPlaces(places) {
          const listEl = document.getElementById("placesList");
          const menuEl = document.getElementById("menu_wrap");
          const fragment = document.createDocumentFragment();
          // const bounds = new window.kakao.maps.LatLngBounds();
          removeAllChildNods(listEl);
          removeMarker();
          for (let i = 0; i < places.length; i++) {
            const placePosition = new window.kakao.maps.LatLng(
              places[i].y,
              places[i].x
            );
            const marker = addMarker(placePosition, i);
            const itemEl = getListItem(i, places[i]);
            // bounds.extend(placePosition);
            (function (marker, title) {
              window.kakao.maps.event.addListener(
                marker,
                "mouseover",
                function () {
                  displayInfowindow(marker, title);
                }
              );

              window.kakao.maps.event.addListener(
                marker,
                "mouseout",
                function () {
                  infowindow.close();
                }
              );

              itemEl.addEventListener("click", function (e) {
                displayInfowindow(marker, title);
                props.setAddress(places[i]);
                map.panTo(placePosition);
              });
            })(marker, places[i].place_name);

            fragment.appendChild(itemEl);
          }

          listEl?.appendChild(fragment);
          menuEl.scrollTop = 0;

          // map.panTo(bounds);
        }

1) listEl이라는 변수에 placesList라는 태그를 담습니다.

여기선 ul태그입니다. 검색결과를 목록화 시킵니다.

2) menuEl이라는 변수에 menu_wrap라는 태그를 담습니다.

여기선 div 태그이고 지도 좌측에 나오는 사이드 바입니다.

3) fragment라는 변수에 Fragment를 만들어 줍니다. <></>

4) bounds와 관련된 것들은 리팩토링 과정에서 중복이 발견되어 삭제하였습니다.

5) removeAllChildNods(listEl)을 통해서 검색 결과 목록에 추가된 항목들을 제거합니다.

6) removeMarker()는 지도에 표시되고 있는 마커를 제거하는 함수입니다.
5),6)은 추가 검색시 전의 검색결과를 제거하는 역할을 합니다.

7) for문을 이용해서 검색결과를 반복해서 보여주게 됩니다.
검색결과는 displayPlaces()의 인자로 들어온 places입니다.

8) for문 내에서 placePosition이라는 변수에 displayPlaces()함수에 들어온 인자값(places)의 좌표를 LatLng()에 넣어줍니다.

9) for문 내에서 marker라는 변수에 addMarker()를 이용해서 마커를 생성합니다. 이 때 addMarker()의 인자값으로 placePosition이 들어오며 마커의 좌표를 나타냅니다.

10) for문 내에서 itemEl이라는 변수에 getListItem()을 이용해서 검색 결과 항목 Element를 생성합니다. 인자인 igetListItem()index값을 places[i]places를 의미합니다.

11) 다음의 함수는 각각 마우스를 호버 했을 때, 아웃했을 때 인포윈도우를 생성, 삭제합니다.

window.kakao.maps.event.addListener(
                marker,
                "mouseover",
                function () {
                  displayInfowindow(marker, title);
                }
              );
window.kakao.maps.event.addListener(
                marker,
                "mouseout",
                function () {
                  infowindow.close();
                }
              );

12) 10)에서 생성된 itemEl(검색결과항목)에 addEventListner를 이용해서 클릭이벤트를 만들어 준뒤 클릭시 중앙으로 이동하도록 만들었습니다.
즉 좌측 리스트의 아이템을 클릭하면 해당 아이템의 마커가 중앙으로 오게 만들었습니다.

13) listElfragment태그에 추가합니다.
즉 검색결과 항목들을 목록(fragment)에 추가하는 것입니다.

자. 검색 결과 항목을 Element로 변환합니다.

function getListItem(index: any, places: any) {
	const el = document.createElement("li");
	let itemStr =
'<span class="markerbg marker_' + (index + 1) +'"></span>' + '<div class="info">' + "<h5>" + places.place_name + "</h5>";
		if (places.road_address_name) {
            itemStr +=
              "    <span>" +
              places.road_address_name +
              "</span>" +
              '   <span class="jibun gray">' +
              `<img src="https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/places_jibun.png">
              </img>` +
              places.address_name +
              "</span>";
          } else {
            itemStr += "<span>" + places.address_name + "</span>";
          }

          itemStr +=
            '  <span class="tel">' + places.phone + "</span>" + "</div>";

          el.innerHTML = itemStr;
          el.className = "item";

          return el;
        }

1) el이라는 변수에 li태그를 만들어서 담습니다.

2) itemStr은 각각의 검색결과의 이름, 도로명주소, 지번주소, 전화번호를 표시하도록 하였고 이는 개발자에 의해 수정된 값입니다.

3) 생성된 itemStrel.innerHTML에 할당되고 classNameitem으로 만들어 줍니다.

4) 이 작업은 좌측의 사이드바에 표시되는 내용입니다.

차. 마커를 생성하고 지도에 표시하기

function addMarker(position: any, idx: any) {
          const imageSrc =
            "https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_number_blue.png";
          const imageSize = new window.kakao.maps.Size(36, 37);
          const imgOptions = {
            spriteSize: new window.kakao.maps.Size(36, 691),
            spriteOrigin: new window.kakao.maps.Point(0, idx * 46 + 10),
            offset: new window.kakao.maps.Point(13, 37),
          };

          const markerImage = new window.kakao.maps.MarkerImage(
            imageSrc,
            imageSize,
            imgOptions
          );

          const marker = new window.kakao.maps.Marker({
            position,
            image: markerImage,
          });

          marker.setMap(map);
          markers.push(marker);

          return marker;
        }

1) 마커이미지와 사이즈 옵션을 조정할 수 있습니다.

2) marker라는 변수에 marker가 표시될 좌표, 이미지들을 할당합니다.

3) marker.setMap(map)을 통해 지도 위에 마커를 표출합니다.

4) markers.push(marker)를 통해 markers배열에 생성된 마커를 추가합니다. 이는 를 참고하시면 됩니다.

카. 기존의 마커들을 제거하는 함수입니다.

function removeMarker() {
          for (let i = 0; i < markers.length; i++) {
            markers[i].setMap(null);
          }
          markers = [];
        }

타. 검색결과 하단부에 페이지번호를 표시하는 함수입니다.

function displayPagination(pagination) {
          const paginationEl = document.getElementById("pagination");
          const fragment = document.createDocumentFragment();
          while (paginationEl?.hasChildNodes()) {
            paginationEl.removeChild(paginationEl.lastChild);
          }

          for (let i = 1; i <= pagination.last; i++) {
            const el = document.createElement("a");
            el.href = "#";
            el.innerHTML = String(i);

            if (i === pagination.current) {
              el.className = "on";
            } else {
              el.onclick = (function (i) {
                return function () {
                  pagination.gotoPage(i);
                };
              })(i);
            }

            fragment.appendChild(el);
          }
          paginationEl?.appendChild(fragment);
        }

1) paginationEl이라는 변수에 id가 pagination을 담습니다.

2) 기존의 추가된 페이지 번호를 삭제하는 함수입니다.

while (paginationEl?.hasChildNodes()) {
            paginationEl.removeChild(paginationEl.lastChild);
          }

3) for문을 이용해서 페이지네이션 작업을 합니다.

4) for문 내에서 el이라는 변수에 a태그를 만들어 담습니다.

5) elhref를 #으로 innerHTML을 통해서 검색결과의 페이지 수로 변경합니다.

6) 만약에 i가 현재의 페이지네이션이라면 elclassNameon으로 변경합니다.

if (i === pagination.current) {
              el.className = "on";
            }

7) 그렇지 않으면 elonclick에는 gotoPage()를 이용해서 선택된 페이지로 이동할수 있게 만듭니다.

else {
              el.onclick = (function (i) {
                return function () {
                  pagination.gotoPage(i);
                };
              })(i);
            }

파. 인포윈도우를 생성합니다.

function displayInfowindow(marker: any, title: any) {
          const content =
            '<div style="padding:5px;z-index:1;">' + title + "</div>";

          infowindow.setContent(content);
          infowindow.open(map, marker);
        }

하. 검색결과 목록의 자식 Element를 제거하는 함수입니다.

function removeAllChildNods(el: any) {
          while (el.hasChildNodes()) {
            el.removeChild(el.lastChild);
          }
        }

1) 에서 검색 결과 목록에 추가된 아이템들을 제거하는 함수입니다.

3. 카카오지도 외

	const [search, setSearch] = useState("");
	const [isOpen, setIsOpen] = useState(true);

	const onchangeSearch = (event) => {
    setSearch(event?.target.value);
	};

	const onClickSearchBarOpen = () => {
    setIsOpen(!isOpen);
	};

가. 검색결과를 제어하는 state입니다.

const [search, setSearch] = useState("");

나. 사이드바 열림 닫힘 상태를 제어하는 state입니다.

const [isOpen, setIsOpen] = useState(true);

다. input값으로 들어오는 검색어를 처리하는 onChange함수입니다.

const onchangeSearch = (event: any) => {
    setSearch(event?.target.value);
	};

라. 사이드바 열림 닫힘을 처리하는 onClick함수 입니다.

const onClickSearchBarOpen = () => {
    setIsOpen(!isOpen);
	};

4. 전체코드

가. script component

import { useEffect, useState } from "react";
import * as S from "./WriteMap.styled";

declare const window: typeof globalThis & {
  kakao: any;
};

export default function WriteMapPage(props: any) {
  useEffect(() => {
    const script = document.createElement("script");
    script.src =
      "//dapi.kakao.com/v2/maps/sdk.js?appkey=f487080ea91748abbd2e3df735d5af4c&libraries=services&autoload=false";
    document.head.appendChild(script);

    script.onload = () => {
      window.kakao.maps.load(function () {
        let markers: any[] = [];

        const container = document.getElementById("map");
        const options = {
          center: new window.kakao.maps.LatLng(38.2313466, 128.2139293),
          level: 1,
        };
        const map = new window.kakao.maps.Map(container, options);

        const markerPosition = new window.kakao.maps.LatLng(
          38.2313466,
          128.2139293
        );

        const marker = new window.kakao.maps.Marker({
          position: markerPosition,
        });

        marker.setMap(map);

        const ps = new window.kakao.maps.services.Places();

        const infowindow = new window.kakao.maps.InfoWindow({ zIndex: 1 });

        const searchForm = document.getElementById("submit_btn");
        searchForm?.addEventListener("click", function (e) {
          e.preventDefault();
          searchPlaces();
        });

        function searchPlaces() {
          const keyword = (
            document.getElementById("keyword") as HTMLInputElement
          ).value;

          if (!keyword.replace(/^\s+|\s+$/g, "")) {
            alert("키워드를 입력해주세요!");
            return false;
          }

          ps.keywordSearch(keyword, placesSearchCB);
        }

        function placesSearchCB(data: any, status: any, pagination: any) {
          if (status === window.kakao.maps.services.Status.OK) {
            displayPlaces(data);

            displayPagination(pagination);

            const bounds = new window.kakao.maps.LatLngBounds();
            for (let i = 0; i < data.length; i++) {
              displayMarker(data[i]);
              bounds.extend(new window.kakao.maps.LatLng(data[i].y, data[i].x));
            }

            map.setBounds(bounds);
          } else if (status === window.kakao.maps.services.Status.ZERO_RESULT) {
            alert("검색 결과가 존재하지 않습니다.");
          } else if (status === window.kakao.maps.services.Status.ERROR) {
            alert("검색 결과 중 오류가 발생했습니다.");
          }
        }

        function displayMarker(place: any) {
          const marker = new window.kakao.maps.Marker({
            map,
            position: new window.kakao.maps.LatLng(place.y, place.x),
          });
          window.kakao.maps.event.addListener(
            marker,
            "click",
            function (mouseEvent: any) {
              props.setAddress(place);
              infowindow.setContent(`
              <span>
              ${place.place_name}
              </span>
              `);
              infowindow.open(map, marker);
              const moveLatLon = new window.kakao.maps.LatLng(place.y, place.x);
              map.panTo(moveLatLon);
            }
          );
        }

        function displayPlaces(places: any) {
          const listEl = document.getElementById("placesList");
          const menuEl = document.getElementById("menu_wrap");
          const fragment = document.createDocumentFragment();
          // const bounds = new window.kakao.maps.LatLngBounds();
          removeAllChildNods(listEl);
          removeMarker();
          for (let i = 0; i < places.length; i++) {
            const placePosition = new window.kakao.maps.LatLng(
              places[i].y,
              places[i].x
            );
            const marker = addMarker(placePosition, i);
            const itemEl = getListItem(i, places[i]);
            // bounds.extend(placePosition);
            (function (marker, title) {
              window.kakao.maps.event.addListener(
                marker,
                "mouseover",
                function () {
                  displayInfowindow(marker, title);
                }
              );

              window.kakao.maps.event.addListener(
                marker,
                "mouseout",
                function () {
                  infowindow.close();
                }
              );

              itemEl.addEventListener("click", function (e) {
                displayInfowindow(marker, title);
                props.setAddress(places[i]);
                map.panTo(placePosition);
              });
            })(marker, places[i].place_name);

            fragment.appendChild(itemEl);
          }

          listEl?.appendChild(fragment);
          menuEl.scrollTop = 0;

          // map.panTo(bounds);
        }

        function getListItem(index: any, places: any) {
          const el = document.createElement("li");

          let itemStr =
            '<span class="markerbg marker_' +
            (index + 1) +
            '"></span>' +
            '<div class="info">' +
            "   <h5>" +
            places.place_name +
            "</h5>";

          if (places.road_address_name) {
            itemStr +=
              "    <span>" +
              places.road_address_name +
              "</span>" +
              '   <span class="jibun gray">' +
              `<img src="https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/places_jibun.png">
              </img>` +
              places.address_name +
              "</span>";
          } else {
            itemStr += "    <span>" + places.address_name + "</span>";
          }

          itemStr +=
            '  <span class="tel">' + places.phone + "</span>" + "</div>";

          el.innerHTML = itemStr;
          el.className = "item";

          return el;
        }

        function addMarker(position: any, idx: any) {
          const imageSrc =
            "https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_number_blue.png";
          const imageSize = new window.kakao.maps.Size(36, 37);
          const imgOptions = {
            spriteSize: new window.kakao.maps.Size(36, 691),
            spriteOrigin: new window.kakao.maps.Point(0, idx * 46 + 10),
            offset: new window.kakao.maps.Point(13, 37),
          };

          const markerImage = new window.kakao.maps.MarkerImage(
            imageSrc,
            imageSize,
            imgOptions
          );

          const marker = new window.kakao.maps.Marker({
            position,
            image: markerImage,
          });

          marker.setMap(map);
          markers.push(marker);

          return marker;
        }

        function removeMarker() {
          for (let i = 0; i < markers.length; i++) {
            markers[i].setMap(null);
          }
          markers = [];
        }

        function displayPagination(pagination: any) {
          const paginationEl = document.getElementById("pagination");
          const fragment = document.createDocumentFragment();
          while (paginationEl?.hasChildNodes()) {
            paginationEl.removeChild(paginationEl.lastChild);
          }

          for (let i = 1; i <= pagination.last; i++) {
            const el = document.createElement("a");
            el.href = "#";
            el.innerHTML = String(i);

            if (i === pagination.current) {
              el.className = "on";
            } else {
              el.onclick = (function (i) {
                return function () {
                  pagination.gotoPage(i);
                };
              })(i);
            }

            fragment.appendChild(el);
          }
          paginationEl?.appendChild(fragment);
        }

        function displayInfowindow(marker: any, title: any) {
          const content =
            '<div style="padding:5px;z-index:1;">' + title + "</div>";

          infowindow.setContent(content);
          infowindow.open(map, marker);
        }

        function removeAllChildNods(el: any) {
          while (el.hasChildNodes()) {
            el.removeChild(el.lastChild);
          }
        }
      });
    };
  }, []);

  const [search, setSearch] = useState("");
  const [isOpen, setIsOpen] = useState(true);

  const onchangeSearch = (event: any) => {
    setSearch(event?.target.value);
  };

  const onClickSearchBarOpen = () => {
    setIsOpen(!isOpen);
  };

  return (
    <S.MapSection className="map_wrap" isOpen={isOpen}>
      <div id="map"></div>

      <div id="menuDiv">
        <div id="menu_wrap" className="bg_white">
          <div className="option">
            <div>
              <div id="map_title">
                <div>단짠맛집</div>
              </div>

              <div id="form">
                <input
                  type="text"
                  value={search}
                  id="keyword"
                  onChange={onchangeSearch}
                />
                <button id="submit_btn" type="submit">
                  <S.SearchIcon />
                </button>
              </div>
            </div>
          </div>

          <ul id="placesList"></ul>
          <div id="pagination"></div>
        </div>

        <div id="btnDiv">
          {isOpen ? (
            <div id="btnOn">
              <button
                id="searchBtn"
                onClick={onClickSearchBarOpen}
                type="button"
              >
                <S.LeftDisplayButton />
              </button>
            </div>
          ) : (
            <div id="btnOn">
              <button
                id="searchBtn"
                onClick={onClickSearchBarOpen}
                type="button"
              >
                <S.RightDisplayButton />
              </button>
            </div>
          )}
        </div>
      </div>
    </S.MapSection>
  );
}

나. styled.component

import styled from "@emotion/styled";
import {
  SearchOutlined,
  CaretLeftFilled,
  CaretRightFilled,
} from "@ant-design/icons";

interface ISearchBarOpen {
  isOpen: boolean;
}

export const MapSection = styled.div`
  display: flex;
  #map {
    width: 920px;
    height: 600px;
    position: absolute;
    overflow: hidden;
    border-radius: 20px;
  }
  #menuDiv {
    display: flex;
    position: relative;
    z-index: 2;
    font-size: 12px;
  }

  #menu_wrap {
    position: relative;
    width: 400px;
    height: 600px;
    border-radius: 20px;
    overflow-y: auto;
    background: rgba(255, 255, 255, 0.7);
    display: ${(props: ISearchBarOpen) => (props.isOpen ? "" : "none")};
  }

  #map_title {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    padding: 10px;
  }

  #form {
    display: flex;
    justify-content: space-between;
    padding: 0px 15px 10px 15px;
  }

  #keyword {
    width: 100%;
    border: none;
    outline: none;
  }

  #submit_btn {
    background-color: #ff6e30;
    border: none;
    outline: none;
  }

  #placesList h5 {
    color: #ff6e30;
  }

  #placesList li {
    list-style: square;
  }
  #placesList .item {
    border-bottom: 1px solid #888;
    overflow: hidden;
    cursor: pointer;
  }

  #placesList .item .info {
    padding: 10px 0 10px 5px;
  }

  #placesList .item span {
    display: block;
    margin-top: 4px;
  }
  #placesList .info .gray {
    color: #8a8a8a;
  }

  #placesList .info .tel {
    color: #009900;
  }

  #btnDiv {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  #pagination {
    margin: 10px auto;
    text-align: center;
  }
  #pagination a {
    display: inline-block;
    margin-right: 10px;
    color: #7b7b7b;
  }
  #pagination .on {
    font-weight: bold;
    cursor: default;
    color: #ff6e30;
  }

  #btnOn {
    height: 600px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  #searchBtn {
    width: 20px;
    padding: 0px;
    height: 70px;
    background-color: #ffa230;
    border: none;
    outline: none;
  }
`;

export const SearchIcon = styled(SearchOutlined)`
  color: #fff;
  cursor: pointer;
`;

export const LeftDisplayButton = styled(CaretLeftFilled)`
  color: #fff;
  cursor: pointer;
`;
export const RightDisplayButton = styled(CaretRightFilled)`
  color: #fff;
`;

팀프로젝트 때 써뒀던 것을 모아둔 포스팅

profile
적는 자만이 생존한다.

2개의 댓글

comment-user-thumbnail
2024년 3월 10일

안녕하세요. 리액트 네이티브로 앱을 개발하고 있는 초보자입니다.
적자생존님의 글처럼 카카오 지도 api를 활용해서 검색하는 기능을 구현하고 싶은데, 이것이 웹 말고 안드로이드 앱에서도 구현이 가능한지 궁금합니다.

이 글은 혹시 리액트 네이티브가 아닌 리액트를 사용해서 하신 건가요??

1개의 답글