React

[TIL/React] 2024/08/14

✅ Overview새로운 프로젝트에 대한 몇 가지 요구사항이 있는데, 핵심은 '지도'와 '검색'이다.방탈출 및 보드게임 카페의 위치를 지도 상에 마커로 표시하고, 사용자의 '지역 검색어' 및 '실시간 위치 정보'를 기반으로, 해당 지역의 방탈출 및 보드게임 카페에 대한

2024년 8월 14일13min read

✅ Overview

새로운 프로젝트에 대한 몇 가지 요구사항이 있는데, 핵심은 '지도'와 '검색'이다.

방탈출 및 보드게임 카페의 위치를 지도 상에 마커로 표시하고, 사용자의 '지역 검색어' 및 '실시간 위치 정보'를 기반으로, 해당 지역의 방탈출 및 보드게임 카페에 대한 정보를 렌더링 하는 것이 일차적인 과업이다.

최초에 Naver Maps API와 Naver Search API를 통해 해당 기능을 구현하려 했다. 그런데 Naver의 Search API(지역 한정)는, 한 번에 표시할 수 있는 검색 결과 개수를 최대 5개로 제한하고 있었다.

서울 지역의 방탈출 및 보드게임 카페에 대한 데이터를 직접 입력하는 것에는 한계가 있고, 크롤링을 하는 것은 현재로서는 기술적 허들이 높을 것이라 판단했다.

그래서 Kakao Maps API를 연구하게 되었다.

✅ Kakao Maps API

1. Assessing Available Resources ✍️

가용한 리소스가 무엇인지 파악하는 게 가장 중요하다. 주어진 범위 내에서 필요한 것과 필요하지 않은 것을 분리하는 작업이 선행되어야 한다는 것이다. Docs에서는 목차를 통해 그 해답에 근접할 수 있다. 다만 세부적인 리소스는 기획의 변경이나 기술적 적용에 따라 변동성이 높기 때문에 최소한의 리소스에 집중하는 것이 좋다.

1-1. 지도 🎯

해당 목차에서는 <지도 생성, 이동, 교통정보, 지형도, 클릭 이벤트, 커스텀 타일셋> 등의 기능을 제공한다. 지금 단계에서 반드시 필요한 기능은 ``1)지도 생성하기`, `2)지도 이동시키기`, `3)지도 정보 얻어오기`` 정도가 되겠다.

#### 1-1-1. 지도 생성하기 🏃

code
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

// 지도를 표시할 div와  지도 옵션으로  지도를 생성합니다
var map = new kakao.maps.Map(mapContainer, mapOption); 

#### 1-1-2. 지도 이동시키기 🏃

code
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다

function setCenter() {            
    // 이동할 위도 경도 위치를 생성합니다 
    var moveLatLon = new kakao.maps.LatLng(33.452613, 126.570888);
    
    // 지도 중심을 이동 시킵니다
    map.setCenter(moveLatLon);
}

function panTo() {
    // 이동할 위도 경도 위치를 생성합니다 
    var moveLatLon = new kakao.maps.LatLng(33.450580, 126.574942);
    
    // 지도 중심을 부드럽게 이동시킵니다
    // 만약 이동할 거리가 지도 화면보다 크면 부드러운 효과 없이 이동합니다
    map.panTo(moveLatLon);            
}        

#### 1-1-3. 지도 정보 얻어오기 🏃

code
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다

// 일반 지도와 스카이뷰로 지도 타입을 전환할 수 있는 지도타입 컨트롤을 생성합니다
var mapTypeControl = new kakao.maps.MapTypeControl();

// 지도 타입 컨트롤을 지도에 표시합니다
map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT);

function getInfo() {
    // 지도의 현재 중심좌표를 얻어옵니다 
    var center = map.getCenter(); 
    
    // 지도의 현재 레벨을 얻어옵니다
    var level = map.getLevel();
    
    // 지도타입을 얻어옵니다
    var mapTypeId = map.getMapTypeId(); 
    
    // 지도의 현재 영역을 얻어옵니다 
    var bounds = map.getBounds();
    
    // 영역의 남서쪽 좌표를 얻어옵니다 
    var swLatLng = bounds.getSouthWest(); 
    
    // 영역의 북동쪽 좌표를 얻어옵니다 
    var neLatLng = bounds.getNorthEast(); 
    
    // 영역정보를 문자열로 얻어옵니다. ((남,서), (북,동)) 형식입니다
    var boundsStr = bounds.toString();
    
    
    var message = '지도 중심좌표는 위도 ' + center.getLat() + ', <br>';
    message += '경도 ' + center.getLng() + ' 이고 <br>';
    message += '지도 레벨은 ' + level + ' 입니다 <br> <br>';
    message += '지도 타입은 ' + mapTypeId + ' 이고 <br> ';
    message += '지도의 남서쪽 좌표는 ' + swLatLng.getLat() + ', ' + swLatLng.getLng() + ' 이고 <br>';
    message += '북동쪽 좌표는 ' + neLatLng.getLat() + ', ' + neLatLng.getLng() + ' 입니다';
    
    // 개발자도구를 통해 직접 message 내용을 확인해 보세요.
    // ex) console.log(message);
}

1-2. 오버레이 🎯

오버레이 파트는, 말 그대로 무엇을 어떻게 오버레이 할 것인지에 대해 다룬다. 핵심은 마커다. 세 가지에 집중했다.

#### 1-2-1. 마커 생성하기 🏃

code
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다

// 마커가 표시될 위치입니다 
var markerPosition  = new kakao.maps.LatLng(33.450701, 126.570667); 

// 마커를 생성합니다
var marker = new kakao.maps.Marker({
    position: markerPosition
});

// 마커가 지도 위에 표시되도록 설정합니다
marker.setMap(map);

// 아래 코드는 지도 위의 마커를 제거하는 코드입니다
// marker.setMap(null);

#### 1-2-2. geolocation으로 마커 표시하기 🏃

code
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 10 // 지도의 확대 레벨 
    }; 

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다

// HTML5의 geolocation으로 사용할 수 있는지 확인합니다 
if (navigator.geolocation) {
    
    // GeoLocation을 이용해서 접속 위치를 얻어옵니다
    navigator.geolocation.getCurrentPosition(function(position) {
        
        var lat = position.coords.latitude, // 위도
            lon = position.coords.longitude; // 경도
        
        var locPosition = new kakao.maps.LatLng(lat, lon), // 마커가 표시될 위치를 geolocation으로 얻어온 좌표로 생성합니다
            message = '<div style="padding:5px;">여기에 계신가요?!</div>'; // 인포윈도우에 표시될 내용입니다
        
        // 마커와 인포윈도우를 표시합니다
        displayMarker(locPosition, message);
            
      });
    
} else { // HTML5의 GeoLocation을 사용할 수 없을때 마커 표시 위치와 인포윈도우 내용을 설정합니다
    
    var locPosition = new kakao.maps.LatLng(33.450701, 126.570667),    
        message = 'geolocation을 사용할수 없어요..'
        
    displayMarker(locPosition, message);
}

// 지도에 마커와 인포윈도우를 표시하는 함수입니다
function displayMarker(locPosition, message) {

    // 마커를 생성합니다
    var marker = new kakao.maps.Marker({  
        map: map, 
        position: locPosition
    }); 
    
    var iwContent = message, // 인포윈도우에 표시할 내용
        iwRemoveable = true;

    // 인포윈도우를 생성합니다
    var infowindow = new kakao.maps.InfoWindow({
        content : iwContent,
        removable : iwRemoveable
    });
    
    // 인포윈도우를 마커위에 표시합니다 
    infowindow.open(map, marker);
    
    // 지도 중심좌표를 접속위치로 변경합니다
    map.setCenter(locPosition);      
}    

#### 1-2-3. 여러개 마커 표시하기 🏃

code
var mapContainer = document.getElementById('map'), // 지도를 표시할 div  
    mapOption = { 
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

var map = new kakao.maps.Map(mapContainer, mapOption); // 지도를 생성합니다
 
// 마커를 표시할 위치와 title 객체 배열입니다 
var positions = [
    {
        title: '카카오', 
        latlng: new kakao.maps.LatLng(33.450705, 126.570677)
    },
    {
        title: '생태연못', 
        latlng: new kakao.maps.LatLng(33.450936, 126.569477)
    },
    {
        title: '텃밭', 
        latlng: new kakao.maps.LatLng(33.450879, 126.569940)
    },
    {
        title: '근린공원',
        latlng: new kakao.maps.LatLng(33.451393, 126.570738)
    }
];

// 마커 이미지의 이미지 주소입니다
var imageSrc = "https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/markerStar.png"; 
    
for (var i = 0; i < positions.length; i ++) {
    
    // 마커 이미지의 이미지 크기 입니다
    var imageSize = new kakao.maps.Size(24, 35); 
    
    // 마커 이미지를 생성합니다    
    var markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize); 
    
    // 마커를 생성합니다
    var marker = new kakao.maps.Marker({
        map: map, // 마커를 표시할 지도
        position: positions[i].latlng, // 마커를 표시할 위치
        title : positions[i].title, // 마커의 타이틀, 마커에 마우스를 올리면 타이틀이 표시됩니다
        image : markerImage // 마커 이미지 
    });
}

1-3. 로드뷰 🎯

로드뷰가 현재 단계에서 필요한가에 대한 고민을 했는데, 재미있어 보이는 게 있어서 연습할 겸 한 가지만 다뤄보기로 한다.

#### 1-3-1. 지도 위 버튼으로 로드뷰 표시하기 🏃

로드뷰 버튼을 클릭하면, 로드뷰 도로 오버레이와 동동이(MapWalker)가 표시된다. 어떻게 MapWalker 이름이 동동이.

code
var overlayOn = false, // 지도 위에 로드뷰 오버레이가 추가된 상태를 가지고 있을 변수
    container = document.getElementById('container'), // 지도와 로드뷰를 감싸고 있는 div 입니다
    mapWrapper = document.getElementById('mapWrapper'), // 지도를 감싸고 있는 div 입니다
    mapContainer = document.getElementById('map'), // 지도를 표시할 div 입니다 
    rvContainer = document.getElementById('roadview'); //로드뷰를 표시할 div 입니다

var mapCenter = new kakao.maps.LatLng(33.45042 , 126.57091), // 지도의 중심좌표
    mapOption = {
        center: mapCenter, // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };

// 지도를 표시할 div와 지도 옵션으로 지도를 생성합니다
var map = new kakao.maps.Map(mapContainer, mapOption);

// 로드뷰 객체를 생성합니다 
var rv = new kakao.maps.Roadview(rvContainer); 

// 좌표로부터 로드뷰 파노라마 ID를 가져올 로드뷰 클라이언트 객체를 생성합니다 
var rvClient = new kakao.maps.RoadviewClient(); 

// 로드뷰에 좌표가 바뀌었을 때 발생하는 이벤트를 등록합니다 
kakao.maps.event.addListener(rv, 'position_changed', function() {

    // 현재 로드뷰의 위치 좌표를 얻어옵니다 
    var rvPosition = rv.getPosition();

    // 지도의 중심을 현재 로드뷰의 위치로 설정합니다
    map.setCenter(rvPosition);

    // 지도 위에 로드뷰 도로 오버레이가 추가된 상태이면
    if(overlayOn) {
        // 마커의 위치를 현재 로드뷰의 위치로 설정합니다
        marker.setPosition(rvPosition);
    }
});

// 마커 이미지를 생성합니다
var markImage = new kakao.maps.MarkerImage(
    'https://t1.daumcdn.net/localimg/localimages/07/2018/pc/roadview_minimap_wk_2018.png',
    new kakao.maps.Size(26, 46),
    {
        // 스프라이트 이미지를 사용합니다.
        // 스프라이트 이미지 전체의 크기를 지정하고
        spriteSize: new kakao.maps.Size(1666, 168),
        // 사용하고 싶은 영역의 좌상단 좌표를 입력합니다.
        // background-position으로 지정하는 값이며 부호는 반대입니다.
        spriteOrigin: new kakao.maps.Point(705, 114),
        offset: new kakao.maps.Point(13, 46)
    }
);

// 드래그가 가능한 마커를 생성합니다
var marker = new kakao.maps.Marker({
    image : markImage,
    position: mapCenter,
    draggable: true
});

// 마커에 dragend 이벤트를 등록합니다
kakao.maps.event.addListener(marker, 'dragend', function(mouseEvent) {

    // 현재 마커가 놓인 자리의 좌표입니다 
    var position = marker.getPosition();

    // 마커가 놓인 위치를 기준으로 로드뷰를 설정합니다
    toggleRoadview(position);
});

//지도에 클릭 이벤트를 등록합니다
kakao.maps.event.addListener(map, 'click', function(mouseEvent){
    
    // 지도 위에 로드뷰 도로 오버레이가 추가된 상태가 아니면 클릭이벤트를 무시합니다 
    if(!overlayOn) {
        return;
    }

    // 클릭한 위치의 좌표입니다 
    var position = mouseEvent.latLng;

    // 마커를 클릭한 위치로 옮깁니다
    marker.setPosition(position);

    // 클락한 위치를 기준으로 로드뷰를 설정합니다
    toggleRoadview(position);
});

// 전달받은 좌표(position)에 가까운 로드뷰의 파노라마 ID를 추출하여
// 로드뷰를 설정하는 함수입니다
function toggleRoadview(position){
    rvClient.getNearestPanoId(position, 50, function(panoId) {
        // 파노라마 ID가 null 이면 로드뷰를 숨깁니다
        if (panoId === null) {
            toggleMapWrapper(true, position);
        } else {
         toggleMapWrapper(false, position);

            // panoId로 로드뷰를 설정합니다
            rv.setPanoId(panoId, position);
        }
    });
}

// 지도를 감싸고 있는 div의 크기를 조정하는 함수입니다
function toggleMapWrapper(active, position) {
    if (active) {

        // 지도를 감싸고 있는 div의 너비가 100%가 되도록 class를 변경합니다 
        container.className = '';

        // 지도의 크기가 변경되었기 때문에 relayout 함수를 호출합니다
        map.relayout();

        // 지도의 너비가 변경될 때 지도중심을 입력받은 위치(position)로 설정합니다
        map.setCenter(position);
    } else {

        // 지도만 보여지고 있는 상태이면 지도의 너비가 50%가 되도록 class를 변경하여
        // 로드뷰가 함께 표시되게 합니다
        if (container.className.indexOf('view_roadview') === -1) {
            container.className = 'view_roadview';

            // 지도의 크기가 변경되었기 때문에 relayout 함수를 호출합니다
            map.relayout();

            // 지도의 너비가 변경될 때 지도중심을 입력받은 위치(position)로 설정합니다
            map.setCenter(position);
        }
    }
}

// 지도 위의 로드뷰 도로 오버레이를 추가,제거하는 함수입니다
function toggleOverlay(active) {
    if (active) {
        overlayOn = true;

        // 지도 위에 로드뷰 도로 오버레이를 추가합니다
        map.addOverlayMapTypeId(kakao.maps.MapTypeId.ROADVIEW);

        // 지도 위에 마커를 표시합니다
        marker.setMap(map);

        // 마커의 위치를 지도 중심으로 설정합니다 
        marker.setPosition(map.getCenter());

        // 로드뷰의 위치를 지도 중심으로 설정합니다
        toggleRoadview(map.getCenter());
    } else {
        overlayOn = false;

        // 지도 위의 로드뷰 도로 오버레이를 제거합니다
        map.removeOverlayMapTypeId(kakao.maps.MapTypeId.ROADVIEW);

        // 지도 위의 마커를 제거합니다
        marker.setMap(null);
    }
}

// 지도 위의 로드뷰 버튼을 눌렀을 때 호출되는 함수입니다
function setRoadviewRoad() {
    var control = document.getElementById('roadviewControl');

    // 버튼이 눌린 상태가 아니면
    if (control.className.indexOf('active') === -1) {
        control.className = 'active';

        // 로드뷰 도로 오버레이가 보이게 합니다
        toggleOverlay(true);
    } else {
        control.className = '';

        // 로드뷰 도로 오버레이를 제거합니다
        toggleOverlay(false);
    }
}

// 로드뷰에서 X버튼을 눌렀을 때 로드뷰를 지도 뒤로 숨기는 함수입니다
function closeRoadview() {
    var position = marker.getPosition();
    toggleMapWrapper(true, position);
}

1-4. 정적 지도 🎯

정적 지도는 활용하지 않을 예정이다.

1-5. 라이브러리 🎯

클러스터나 툴 박스의 필요성은 아직 못 느끼겠다.

키워드로 장소 검색하고 목록으로 표출하기``` 부분이 사실 가장 원하던 기능이다. 다만 지도상에 표출되는 목록 데이터를, 직접 활용할 수 있는지는 읽어봐야 알 것 같다.

#### 1-5-1. 키워드로 장소 검색하고 목록으로 표출하기 🏃

// 마커를 담을 배열입니다 var markers = [];

var mapContainer = document.getElementById('map'), // 지도를 표시할 div mapOption = { center: new kakao.maps.LatLng(37.566826, 126.9786567), // 지도의 중심좌표 level: 3 // 지도의 확대 레벨 };

// 지도를 생성합니다 var map = new kakao.maps.Map(mapContainer, mapOption);

// 장소 검색 객체를 생성합니다 var ps = new kakao.maps.services.Places();

// 검색 결과 목록이나 마커를 클릭했을 때 장소명을 표출할 인포윈도우를 생성합니다 var infowindow = new kakao.maps.InfoWindow({zIndex:1});

// 키워드로 장소를 검색합니다 searchPlaces();

// 키워드 검색을 요청하는 함수입니다 function searchPlaces() {

var keyword = document.getElementById('keyword').value;

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

// 장소검색 객체를 통해 키워드로 장소검색을 요청합니다 ps.keywordSearch( keyword, placesSearchCB); }

// 장소검색이 완료됐을 때 호출되는 콜백함수 입니다 function placesSearchCB(data, status, pagination) { if (status === kakao.maps.services.Status.OK) {

// 정상적으로 검색이 완료됐으면 // 검색 목록과 마커를 표출합니다 displayPlaces(data);

// 페이지 번호를 표출합니다 displayPagination(pagination);

} else if (status === kakao.maps.services.Status.ZERO_RESULT) {

alert('검색 결과가 존재하지 않습니다.'); return;

} else if (status === kakao.maps.services.Status.ERROR) {

alert('검색 결과 중 오류가 발생했습니다.'); return;

} }

// 검색 결과 목록과 마커를 표출하는 함수입니다 function displayPlaces(places) {

var listEl = document.getElementById('placesList'), menuEl = document.getElementById('menu_wrap'), fragment = document.createDocumentFragment(), bounds = new kakao.maps.LatLngBounds(), listStr = '';

// 검색 결과 목록에 추가된 항목들을 제거합니다 removeAllChildNods(listEl);

// 지도에 표시되고 있는 마커를 제거합니다 removeMarker();

for ( var i=0; i

// 마커를 생성하고 지도에 표시합니다 var placePosition = new kakao.maps.LatLng(places[i].y, places[i].x), marker = addMarker(placePosition, i), itemEl = getListItem(i, places[i]); // 검색 결과 항목 Element를 생성합니다

// 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 // LatLngBounds 객체에 좌표를 추가합니다 bounds.extend(placePosition);

// 마커와 검색결과 항목에 mouseover 했을때 // 해당 장소에 인포윈도우에 장소명을 표시합니다 // mouseout 했을 때는 인포윈도우를 닫습니다 (function(marker, title) { kakao.maps.event.addListener(marker, 'mouseover', function() { displayInfowindow(marker, title); });

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

itemEl.onmouseover = function () { displayInfowindow(marker, title); };

itemEl.onmouseout = function () { infowindow.close(); }; })(marker, places[i].place_name);

fragment.appendChild(itemEl); }

// 검색결과 항목들을 검색결과 목록 Element에 추가합니다 listEl.appendChild(fragment); menuEl.scrollTop = 0;

// 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다 map.setBounds(bounds); }

// 검색결과 항목을 Element로 반환하는 함수입니다 function getListItem(index, places) {

var el = document.createElement('li'), itemStr = '' + '

' + '
' + places.place_name + '
';

if (places.road_address_name) { itemStr += ' ' + places.road_address_name + '' + ' ' + places.address_name + ''; } else { itemStr += ' ' + places.address_name + ''; }

itemStr += ' ' + places.phone + '' + '

';

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

return el; }

// 마커를 생성하고 지도 위에 마커를 표시하는 함수입니다 function addMarker(position, idx, title) { var imageSrc = 'https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_number_blue.png', // 마커 이미지 url, 스프라이트 이미지를 씁니다 imageSize = new kakao.maps.Size(36, 37), // 마커 이미지의 크기 imgOptions = { spriteSize : new kakao.maps.Size(36, 691), // 스프라이트 이미지의 크기 spriteOrigin : new kakao.maps.Point(0, (idx46)+10), // 스프라이트 이미지 중 사용할 영역의 좌상단 좌표 offset: new kakao.maps.Point(13, 37) // 마커 좌표에 일치시킬 이미지 내에서의 좌표 }, markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imgOptions), marker = new kakao.maps.Marker({ position: position, // 마커의 위치 image: markerImage });

marker.setMap(map); // 지도 위에 마커를 표출합니다 markers.push(marker); // 배열에 생성된 마커를 추가합니다

return marker; }

// 지도 위에 표시되고 있는 마커를 모두 제거합니다 function removeMarker() { for ( var i = 0; i < markers.length; i++ ) { markers[i].setMap(null); } markers = []; }

// 검색결과 목록 하단에 페이지번호를 표시는 함수입니다 function displayPagination(pagination) { var paginationEl = document.getElementById('pagination'), fragment = document.createDocumentFragment(), i;

// 기존에 추가된 페이지번호를 삭제합니다 while (paginationEl.hasChildNodes()) { paginationEl.removeChild (paginationEl.lastChild); }

for (i=1; i<=pagination.last; i++) { var el = document.createElement('a'); el.href = "#"; el.innerHTML = 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, title) { var content = '

' + title + '
';

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

// 검색결과 목록의 자식 Element를 제거하는 함수입니다 function removeAllChildNods(el) { while (el.hasChildNodes()) { el.removeChild (el.lastChild); } }