커스텀 오버레이 마우스오버 이벤트 문의 드립니다

안녕하세요. 저번에 Ajax로 지도에 마커 및 오버레이 생성을 질문 드렸던 초급 개발자입니다.

다름이 아니라, 이번에는 커스텀 오버레이에 이벤트를 추가하고 싶은데
며칠째 생각대로 잘 되지 않아서 문의를 드리게 되었습니다.

제가 구현하려는 시나리오는 다음과 같습니다.

  1. 지도에 생성한 마커에 마우스를 올리면(mouseover), 해당 마커의 커스텀 오버레이가 나타난다.
  2. 커스텀 오버레이에서 마우스가 벗어나면 커스텀 오버레이가 사라진다.

다음은 시나리오를 위해 제가 작성한 코드입니다.

// 사이트에 등록된 모든 체험마을 지도에서 조회하기		
	var showAllTowns = function(){
		$.ajax({
			url : '${pageContext.request.contextPath}/town/api/showAll',
			type : 'GET',
			dataType : 'json',
			data : '',
			success : function(response) {
				if(response.result == 'fail') {
					console.log(response.message);
					return;
				}
								
				$('#map').empty();				
				map = new kakao.maps.Map(mapContainer, mapOption);
				
				// 일반 지도와 스카이뷰로 지도 타입을 전환할 수 있는 지도타입 컨트롤을 생성합니다
				var mapTypeControl = new kakao.maps.MapTypeControl();

				// 지도에 컨트롤을 추가해야 지도위에 표시됩니다
				// kakao.maps.ControlPosition은 컨트롤이 표시될 위치를 정의하는데 TOPRIGHT는 오른쪽 위를 의미합니다
				map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT);

				// 지도 확대 축소를 제어할 수 있는  줌 컨트롤을 생성합니다
				var zoomControl = new kakao.maps.ZoomControl();
				map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT);
								
				if(response.result == 'success') {
					// 마을 전체 리스트를 돌면서 마을 위치마다 마커 찍기
					$.each(response.data, function(index, value){
						// DB에서 주소를 불러와 geocoder를 이용해 좌표로 변환
						geocoder.addressSearch(response.data[index].townAddr, function(result, status) {
							// 정상적으로 검색이 완료됐으면 
							if (status === kakao.maps.services.Status.OK) {
								var coords = new kakao.maps.LatLng(result[0].y, result[0].x);
	
								// 마커가 표시될 위치입니다
								var markerPosition  = new kakao.maps.LatLng(coords.Ha, coords.Ga); 
	
								// 마커를 생성합니다
								var marker = new kakao.maps.Marker({
									image: clamImage,
								    position: markerPosition
								});
									
								// 커스텀 오버레이를 닫는 함수입니다.
								var closeOverlay = function() {
								    overlay.setMap(null);
								};
																	
								// 커스텀 오버레이에 표시할 컨텐츠 입니다
								// 커스텀 오버레이는 아래와 같이 사용자가 자유롭게 컨텐츠를 구성하고 이벤트를 제어할 수 있기 때문에
								// 별도의 이벤트 메소드를 제공하지 않습니다
								var $box = 		$('<div class="box"/>');
								var $wrap =		$('	<div class="wrap"/>');
								var $info = 	$('	 <div class="info"/>');
								var $title = 	$('	  <div class="title"/>').text(response.data[index].townName);
								var $body = 	$('<div class="body"/>');
								var $contents = 
									$('<div class="img">' + 
									'		<img src="http://cfile181.uf.daum.net/image/250649365602043421936D" width="73" height="70">' + 
									'</div>' + 
							        '<div class="desc"">' + 
							        '	<div class="ellipsis">' + response.data[index].townAddr + '</div>' + 
							        '	<div>' + 
							        '		<a href="${pageContext.request.contextPath}/town/' + response.data[index].townURL + 
							        '		" target="_blank" class="link">/' + response.data[index].townURL + '</a>' +
							        '	</div>' + 
							        '</div>');							
										
								$box.append($wrap);
								$wrap.append($info);
								$info.append($title).append($body);
								$body.append($contents);
								
								// 커스텀 오버레이 내용
								var content = $box[0];
								
								// 마커와 커스텀 오버레이를 감싸는 영역
								var wrap = $wrap[0];
								            
								// 커스텀 오버레이 생성
								var overlay = new kakao.maps.CustomOverlay({
								    content: content,
								    map: map,
								    position: marker.getPosition()
								});
																									
								// 마커에 마우스를 올렸을 때 커스텀 오버레이를 표시
								kakao.maps.event.addListener(marker, 'mouseover', function() {
									// 기존에 표시된 커스텀 오버레이가 존재할 경우
									if(selectedOverlay != null && selectedMarker != null) {
										selectedOverlay.setMap(null);			// 기존 커스텀 오버레이를 닫는다
										selectedMarker.setImage(clamImage);		// 기존 마커를 기존 마커 이미지로 변경
										selectedMarker.setZIndex(1);			// 기존 마커를 지도 상에서 아래로 내린다
									}
									// 현재 마우스오버 이벤트가 발생한 마커와 커스텀 오버레이를 변수에 담아 맵에 표시한다
									selectedOverlay = overlay;
									selectedMarker = marker;

									// z-index 변경 : 선택된 마커와 커스텀 오버레이를 지도 상에서 가장 위로 올린다
									selectedOverlay.setZIndex(10);
								    selectedMarker.setZIndex(10);
								    
								 	// 선택된 마커의 커스텀 오버레이를 표시한다
								    selectedOverlay.setMap(map);
								 	
								 	// 선택된 마커를 새로운 마커 이미지로 변경한다
								    selectedMarker.setImage(clamImage_selected);
								    
								 	// 커스텀 오버레이의 내용을 감싸는 영역인 wrap에 이벤트 등록
								 	// wrap 영역에서 마우스가 벗어나면
								 	wrap.addEventListener('mouseout', function(){
								 		// 커스텀 오버레이가 사라진다
							 			selectedOverlay.setMap(null);
								 		
								 		// 선택된 마커의 이미지를 원래 이미지로 변경
							 			selectedMarker.setImage(clamImage);
							 		}, true);
								 	
								});
																											
								// 펼쳐진 커스텀 오버레이 닫기
								closeOverlay();
								
								// 마커가 지도 위에 표시되도록 설정합니다
								marker.setMap(map);
								
								// 생성된 마커를 배열에 추가
								markers.push(marker);								
							} 
						});
					})
				}
			}
		});
	}

wrap

마커에 마우스를 올렸을 때 해당 마커의 커스텀 오버레이가 표시되고,
다른 마커를 선택했을 땐 기존에 표시된 커스텀 오버레이를 닫고 새로 선택된 커스텀 오버레이가 잘 표시됩니다.
문제는 커스텀 오버레이를 감싸는 wrap이란 영역을 만든 뒤 마우스아웃 이벤트를 등록하여
커스텀 오버레이 영역 밖으로 마우스가 이동하면 커스텀 오버레이를 사라지게 하고 싶었는데
이벤트가 원하는 대로 수행되지 않습니다.

뭔가 되게 간단히 될 거 같은데 아직 경험과 지식이 부족한 탓에 개발자분들의 조언을 구하고자 합니다.
어느 부분(아이디어 또는 코드)이 잘못됐는지 혹은 공부할 개념 등 어떤 조언이든 감사히 받겠습니다.

긴 글 읽어주셔서 감사합니다.

CustomOverlay에 주는 yAnchor 값을 1.XX 로 1 이상 값으로 넣어주시는게 맞습니다. wrap의 height를 키우면 안 되고요.
http://apis.map.kakao.com/web/documentation/#CustomOverlay

1개의 좋아요

소중한 답변 감사합니다. 제가 아까 이것저것 해봤는데
content 부분에 mouseover 대신 mouseleave 이벤트를 등록하니 간단히 해결됐네요.
앞으론 혼자 이것저것 더 시도해보려는 노력을 키워야겠습니다 ㅠ

삽질이 중요하긴 하죠 ㅋㅋ

해결된 상황을 보아하니
저도 뭔가 엉뚱한 답변을 한 것 같아 죄송하게 생각합니다.

막히면 부담없이 질문하세요. 좋은 질문은 언제나 환영입니다.

아 그럼 죄송한데 하나만 더 질문 드리겠습니다 ㅠ

overlay

마커에 mouseover 이벤트를 걸어서 커스텀 오버레이를 맵에 띄우고
커스텀 오버레이에 mouseleave 이벤트를 거니
①에서 ②로 마우스가 움직일 때까진 오버레이가 잘 유지되고
②에서 ③(오버레이 바깥 영역)으로 이동하니 오버레이가 제 의도대로 잘 사라집니다.

근데 문제는 사용자 입장에서 마커에 마우스를 올렸는데
오버레이에 마우스를 올리지 않고 바로 다른 영역으로 마우스를 이동했을 때,
즉 ①에서 ②로 가지 않고, ①에서 ③으로 가는 경우입니다.
그래서 마커에 mouseout 시 커스텀 오버레이를 사라지게 하는 이벤트 핸들러를 추가했습니다.

그런데 이렇게 처리하니 마커와 오버레이 사이의 영역,
그러니까 첨부한 그림의 노란색 영역에서
오버레이가 나타났다가 사라지는 것이 미친듯이 반복되고 있습니다.
커스텀 오버레이의 위치를 마커랑 붙여보기도 하고 (오버레이의 css bottom 값 수정)
답변주신 커스텀 오버레이의 yAnchor 값을 1 이상으로 올려봤는데 별다른 변화는 없었습니다.

질문을 요약하자면, 마우스 위치가

  1. 마커 위에 유지될 때 (①)
  2. 마커 위에서 오버레이로 이동할 때 (① → ②)
  3. 오버레이 내부에 위치할 때 (②)

위의 3가지 경우에만 오버레이가 나타나서 유지되도록 하려면
어떻게 이벤트를 거는 것이 좋을지 궁금합니다.

제가 이 상황이라면
2가지 방식으로 풀어봤을 듯 하네요.

  1. 해당 인터렉션을 회피합니다.
    그냥 클릭 인터렉션으로 만드는게 낫습니다.
    화면에 표시된 커스텀 오버레이를 기억해 두고 있다가
    다른 마커를 클릭하거나, 마커가 아닌 지도 영역을 클릭했을 때 화면에서 없애주는 방식을 넣어주면 충분해 보입니다.

  2. 정공법입니다. 기능 그대로를 구현하는 것인데요.
    자칫하면 정신없는 이벤트 핸들러의 대환장 파티가 일어 날 수 있는 인터렉션 입니다.
    이 경우에는 setTimeout을 사용합니다. 마우스가 다음 요소의 이벤트를 발생시키는데 걸리는 찰나의 시간동안 이벤트 발동을 지연시키기 위해 사용합니다. 이 지연된 이벤트를 실행시킬지 여부는 이후에 발생된 이벤트에서 평가하기 때문에 깜빡이는 현상을 없애 줍니다.

     var mapContainer = document.getElementById('map'),
         mapOption = { 
             center: new kakao.maps.LatLng(33.450701, 126.570667),
             level: 3
         };
    
     var map = new kakao.maps.Map(mapContainer, mapOption);
      
     var places = [
         {
             id: 1,
             name: '카카오', 
             latlng: new kakao.maps.LatLng(33.450705, 126.570677)
         },
         {
             id: 2,
             name: '생태연못', 
             latlng: new kakao.maps.LatLng(33.450936, 126.569477)
         },
         {
             id: 3,
             name: '텃밭', 
             latlng: new kakao.maps.LatLng(33.450879, 126.569940)
         },
         {
             id: 4,
             name: '근린공원',
             latlng: new kakao.maps.LatLng(33.451393, 126.570738)
         }
     ];
    
     var activeId = null;
     var timeoutId = null;
         
     places.forEach(function(place) {
         var id = place.id;
         var position = place.latlng;
         var name = place.name;
         
         var marker = new kakao.maps.Marker({
             map: map,
             position: position 
         });
    
         var content = document.createElement('div');
         content.innerHTML = name;
         content.style.cssText = 'background-color: white';
         
         var overlay = new kakao.maps.CustomOverlay({
             yAnchor: 2.5,
             content: content,
             position: position
         });
         
         var mouseOverHandler = function() {
             if (timeoutId !== null && id === activeId) {
                 window.clearTimeout(timeoutId);
                 timeoutId = null;
                 return;
             }
             overlay.setMap(map);
             activeId = id;
         };
         
         var mouseOutHandler = function() {
             timeoutId = window.setTimeout(function() {
                 overlay.setMap(null);
                 activeId = null;
                 timeoutId = null;
             }, 50);
         };
         
         kakao.maps.event.addListener(marker, 'mouseover', mouseOverHandler);
         kakao.maps.event.addListener(marker, 'mouseout', mouseOutHandler);
         
         content.addEventListener('mouseover', mouseOverHandler);
         content.addEventListener('mouseout', mouseOutHandler);
     });
    

    커스텀 오버레이가 사라지는데 약간의 딜레이가 있지만 크게 거슬리지 않을 겁니다.

1개의 좋아요

저도 그냥 클릭으로 하고 싶었습니다만… ㅠ
정말 자세한 답변 감사합니다. 덕분에 오늘도 많이 배워갑니다 (__)