-
Notifications
You must be signed in to change notification settings - Fork 2
[FE] 산책기능 (작성중)
https://developer.mozilla.org/ko/docs/Web/API/Geolocation_API/Using_the_Geolocation_API
-
scroll-snap-type: x mandatory;
-
-webkit-overflow-scrolling: touch;
const StyledSlideWrapper = styled.div`
margin-top: 1rem;
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
width: 100%;
height: 80%;
`;
left
속성과 transform
속성을 사용하여 요소를 수평으로 중앙에 위치시키는 방법을 정리해보겠습니다.
세 가지 다른 CSS 설정의 결과를 볼 수 있습니다.
- 파란 박스:
left: 0
- 빨간 박스:
left: 50%
- 초록 박스:
left: 50%
와transform: translateX(-50%)
- 요소를 부모 컨테이너의 왼쪽 끝에 위치시킵니다.
- 예상대로 작동하며, 요소의 왼쪽 가장자리가 부모의 왼쪽 끝과 일치합니다.
- 요소의 왼쪽 가장자리를 부모 컨테이너의 중앙에 위치시킵니다.
- 하지만 요소의 중심은 중앙선보다 오른쪽에 있게 됩니다. (이 부분을 캐치를 못하여 중앙에 위치시킨다고 생각했습니다.)
- 요소의 중심을 부모 컨테이너의 중앙선과 정확히 일치시킵니다.
-
left: 50%
로 요소의 왼쪽 가장자리를 중앙에 위치시킨 후, -
transform: translateX(-50%)
로 요소를 왼쪽으로 자신의 너비의 절반만큼 이동시킵니다.
.parent {
position: relative;
width: 300px;
height: 200px;
}
.left-0 {
position: absolute;
left: 0;
}
.left-50-percent {
position: absolute;
left: 50%;
}
.centered {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
left
속성만으로는 요소의 크기를 고려하지 않습니다. transform: translateX(-50%)
를 추가로 사용하면 요소의 크기에 관계없이 정확한 중앙 정렬을 할 수 있습니다.
-
transform
은 GPU 가속을 받아 성능상 이점이 있습니다. -
position: absolute
와left
를 사용하여 대략적인 위치를 잡고,transform
으로 미세 조정을 하는 방식이 효율적입니다.
이 방식을 사용하면 레이아웃 재계산(리플로우)을 최소화하면서 정확한 중앙 정렬을 달성할 수 있습니다.
스톱워치를 구현할 때 주의할 점은 React의 상태 업데이트와 비동기 로직의 상호작용입니다.
StrictMode
로 인해 발생할 수 있는 문제도 고려해야 하며, 타이머의 중복 실행과 메모리 누수를 방지하는 코드 작성이 중요합니다
-
문제 설명 React의
StrictMode
는 개발 모드에서 컴포넌트를 두 번 렌더링함으로써 잠재적인 버그를 찾을 수 있게 해줍니다.
하지만 타이머(setTimeout
또는setInterval
)를 사용하는 경우, 타이머가 두 번 설정되어 시간이 두 배로 빠르게 흐르는 문제가 발생했습니다. -
해결 방법
-
타이머 중복 방지:
useRef
를 사용해 타이머 ID를 관리하고, 중복 설정을 방지하였습니다. -
useCallback
을 사용하여 함수 메모이제이션: 타이머를 설정하는 함수(startTimer
등)를useCallback
으로 감싸 재정의되지 않게 하여useEffect
가 함수의 최신 버전을 참조할 수 있게 하였습니다.
-
타이머 중복 방지:
useEffect(() => {
if (status === 'start' && !timeoutRef.current) {
startTimer();
} else if (status === 'pause') {
stopTimer();
} else if (status === 'stop') {
stopTimer();
setTime(0);
}
return () => stopTimer(); // 컴포넌트 언마운트 시 타이머 정리
}, [status]);
const startTimer = useCallback(() => {
timeoutRef.current = window.setTimeout(() => {
setTime(prev => prev + 1);
startTimer();
}, 1000);
}, []);
-
문제 설명 타이머의 상태를
setTimeout
이나setInterval
로 관리할 때, 상태 업데이트가 비동기로 이루어지기 때문에 의도치 않게 여러 타이머가 실행될 수 있습니다. 또한, 상태 업데이트가 즉시 반영되지 않아 시간이 두 배로 흐르거나 중지되지 않는 현상이 발생할 수 있습니다. -
해결 방법
-
타이머 설정을
useRef
로 관리: 타이머의 ID를useRef
로 관리하여 매 렌더링 시 타이머 상태를 유지할 수 있게 합니다. -
상태 변경 시 타이머를 즉시 정리: 타이머가 재설정되기 전에
clearTimeout
또는clearInterval
로 이전 타이머를 정리합니다. timeoutRef.current 도 null처리를 해줘야 재시작시 startTimer()를 호출할 수 있습니다.
-
타이머 설정을
useEffect(() => {
if (status === 'start' && !timeoutRef.current) {
startTimer();
return;
}
if (status === 'pause') {
stopTimer();
timeoutRef.current = null;
return;
}
if (status === 'stop') {
stopTimer();
setTime(0);
timeoutRef.current = null;
}
return () => stopTimer();
}, [status, startTimer, stopTimer]);
startTimer, stopTimer를 의존성 배열에 추가해줘야합니다.
-
문제 설명 스톱워치에서 초 단위로 기록된 시간을 시, 분, 초 단위로 변환해야 할 때, 수동으로 이를 계산하는 로직이 번거로웠습니다.
-
해결 방법
- 헬퍼 함수로 시간 변환 로직을 분리: 시간 형식 변환을 헬퍼 함수로 분리하여 코드의 가독성을 높이고 재사용성을 증가시켰습니다.
export function getTimeFormatString(time: number): string {
const hour = Math.floor(time / 3600).toString().padStart(2, '0');
const min = Math.floor((time % 3600) / 60).toString().padStart(2, '0');
const sec = (time % 60).toString().padStart(2, '0');
return `${hour}:${min}:${sec}`;
}
-
문제 설명 컴포넌트가 언마운트될 때, 타이머가 여전히 실행 중이라면 메모리 누수가 발생하거나 예기치 않은 동작이 일어날 수 있습니다.
-
해결 방법
-
useEffect
에서 반환 함수로 타이머 정리:useEffect
에서 반환되는 함수로 타이머를 명시적으로 정리하여 컴포넌트가 언마운트될 때 타이머가 중지되도록 하였습니다.
-
useEffect(() => {
return () => stopTimer(); // 컴포넌트 언마운트 시 타이머 정리
}, []);
기존 코드에서 closeOverlay
함수는 오버레이의 content
에 포함된 HTML 문자열 내에서 onclick
속성으로 호출되었습니다.
const customContents = `
<div class="wrap">
<div class="close" onclick="closeOverlay()" title="닫기">
<img src=${closeIcon}>
</div>
</div>`;
-
문제:
- 이 방식에서
closeOverlay
함수는 HTML 문자열 내에서 호출되는데, 브라우저는 HTML 문자열로부터 해당 함수가 정의된 위치를 바로 찾지 못합니다. 그 이유는 문자열로 작성된 함수는 전역 스코프에서 접근 가능해야 하기 때문입니다. - 그러나
closeOverlay
는initMap
함수 안에 지역적으로 정의된 함수였으므로, HTML 문자열 내에서onclick
으로는 함수에 접근할 수 없고, ReferenceError: closeOverlay is not defined 에러가 발생했습니다.
- 이 방식에서
-
문제 요약:
- HTML 문자열 방식으로 함수를 사용하려면 전역 스코프에 함수를 선언해야 합니다. 하지만, 이는 네임스페이스 오염과 다른 함수들과의 충돌 가능성을 높일 수 있습니다.
수정된 코드에서는 closeOverlay
를 HTML 문자열 내에서 직접 호출하지 않고, DOM 요소를 직접 생성한 후 이벤트 리스너를 추가했습니다.
const closeButton = document.createElement('img');
closeButton.src = closeIcon;
closeButton.style.cssText = 'width: 0.8rem; height: 0.8rem; margin-left: 0.2rem; cursor: pointer;';
// 닫기 버튼에 클릭 이벤트를 추가
closeButton.addEventListener('click', () => {
overlay.setMap(null); // 이벤트 리스너로 함수 실행
});
-
차이점:
- HTML 문자열 내에서
onclick
으로 직접 호출하지 않고, DOM 요소를 생성한 후 해당 요소에 이벤트 리스너를 등록했습니다. - 이 방식에서는 함수가 스코프 문제 없이 이벤트 리스너 내부에서 호출되므로, 함수 참조에 문제가 생기지 않습니다.
- HTML 문자열 내에서
변경된 점은 함수 참조 방식입니다:
- 기존 방식에서는 HTML 문자열 내에서
onclick="closeOverlay()"
처럼 함수 호출이 이루어졌습니다. 이때 전역 스코프에서 함수를 찾지 못해 에러가 발생했습니다. - 수정된 방식에서는 DOM 요소를 생성한 후
addEventListener
로 이벤트 리스너를 등록함으로써, 해당 함수가 스코프 내에서 정상적으로 호출될 수 있게 처리했습니다.
결론
-
기존 방식은 전역 스코프에서 함수 참조 문제로 인해
closeOverlay
가 호출되지 않았습니다. -
수정 방식은 DOM 요소에 직접 이벤트 리스너를 추가하여, 함수 참조와 스코프 문제를 해결하고
closeOverlay
함수가 정상적으로 실행되도록 처리한 방식입니다.
따라서 함수 참조 방식의 차이로 인해, closeOverlay
함수가 정상적으로 실행되었습니다.
**kakao.maps.CustomOverlay
**에서 오버레이의 위치를 세밀하게 조정하려면 xAnchor
와 yAnchor
를 사용할 수 있습니다. 이 두 값은 오버레이의 기준점을 조정해, 오버레이가 마커나 지도 위에 어느 방향으로 배치될지 결정합니다.
-
xAnchor
: 오버레이의 가로축 기준점입니다.-
xAnchor: 0
이면, 오버레이의 왼쪽 끝이 마커의 좌표와 일치합니다. -
xAnchor: 0.5
는 오버레이의 가운데가 마커와 일치하는 기본값입니다. -
xAnchor: 1
이면, 오버레이의 오른쪽 끝이 마커와 일치합니다.
-
-
yAnchor
: 오버레이의 세로축 기준점입니다.-
yAnchor: 0
이면, 오버레이의 위쪽 끝이 마커의 좌표와 일치합니다. -
yAnchor: 0.5
는 오버레이의 가운데가 마커와 일치하는 기본값입니다. -
yAnchor: 1
이면, 오버레이의 아래쪽 끝이 마커의 좌표와 일치합니다. -
yAnchor
의 값이 1보다 크면 오버레이가 마커보다 더 위쪽에 위치하게 됩니다.
-
2. 공식 문서 설명
공식 문서에 따르면, xAnchor
와 yAnchor
의 값에 따라 오버레이가 마커를 기준으로 어느 방향에 배치될지 조정할 수 있습니다.
// 커스텀 오버레이를 생성합니다
var mapCustomOverlay = new kakao.maps.CustomOverlay({
position: position,
content: content,
xAnchor: 0.5, // 커스텀 오버레이의 x축 위치입니다. 0.5가 기본값으로 가운데 정렬입니다.
yAnchor: 1.1 // 커스텀 오버레이의 y축 위치입니다. 1.1이면 마커의 위쪽에 위치하게 됩니다.
});
// 마커 이미지 생성
const imageSize = new kakao.maps.Size(65, 65);
const imageOption = { offset: new kakao.maps.Point(27, 69) };
const markerImage = new kakao.maps.MarkerImage(mapMarkerImage, imageSize, imageOption);
// 마커 위치
const markerPosition = new kakao.maps.LatLng(currentLocation[0], currentLocation[1]);
const newMarker = new kakao.maps.Marker({
position: markerPosition,
image: markerImage,
map: mapInstance,
});
// 커스텀 오버레이 콘텐츠 생성
const customContents = `
<div class="wrap" style="padding: 10px; background: white; border-radius: 10px;">
<div class="info">
<div class="title">커스텀 오버레이</div>
<div class="desc">설명 텍스트</div>
</div>
</div>
`;
// 커스텀 오버레이 생성
const overlay = new kakao.maps.CustomOverlay({
content: customContents,
map: mapInstance,
position: newMarker.getPosition(),
xAnchor: 0,
yAnchor: 2,
});
// 마커 클릭 시 오버레이 표시
kakao.maps.event.addListener(newMarker, 'click', function () {
overlay.setMap(mapInstance);
});
-
xAnchor
: 오버레이의 가로 기준점으로,0
일 때는 왼쪽 끝,0.5
는 중앙,1
은 오른쪽 끝이 기준점이 됩니다. -
yAnchor
: 오버레이의 세로 기준점으로,0
일 때는 위쪽 끝,0.5
는 중앙,1
은 아래쪽 끝이 기준점이 됩니다.1
이상의 값은 마커 위로 더 이동합니다. -
목표: 마커 위에 오버레이를 띄우고 싶다면,
yAnchor
를 1보다 큰 값으로 설정하면 됩니다.