카카오맵에 그린 도형 캡처하기 (html2canvas, canvas API)
이번 프로젝트에서 개인적으로 가장 중요하게 생각했던 기능 중 하나는 `카카오 맵 api를 활용해서 내가 원하는 산책로`를 만드는 것이었다.
카카오맵 api 중에 `선의 거리`, `Drawing Library`를 활용해서 내 위치 기반으로 산책로를 그릴 수 있게 구현을 했고,
점을 찍을때 마다 db.json에 `경도`와 `위도`를 저장시킨 후, `원, 선 사각형 다각형 표시하기` 기능을 활용해 지도 위에 직접 그린 산책로를 보여줄 수 있게 되었다.
추후 SNS에 공유할 수 있겠다는 생각에 코스가 그려진 지도를 캡쳐하면 좋겠다라는 생각이 들었다.
그래서 html2Canvas 라이브러리를 활용해 코스를 캡쳐해보기로!!
html2cnavas
캡처할 영역 (DOM)을 지정하고 해당 영역에 대한 스크린샷을 찍을 수 있도록 도와주는 라이브러리이다.
설치
npm install html2canvas
yarn add html2canvas
import
import html2canvas from "html2canvas";
적용 예시 코드
const onClickDownloadButton = () => {
const target = document.getElementById("download");
if (!target) {
return alert("사진 저장에 실패했습니다.");
}
html2canvas(target).then((canvas) => {
const link = document.createElement("a");
document.body.appendChild(link);
link.href = canvas.toDataURL("image/png");
link.download = "monggleroad.png"; // 다운로드 이미지 파일 이름
link.click();
document.body.removeChild(link);
});
};
- id가 "download" 로 감싸여 있는 부분을 찾아 캡처하는 코드
- html2canvas 라이브러리를 사용하여 target 요소를 캡처
- onClick 이벤트를 줘서 클릭하면 해당 함수가 실행되도록 설정
그리고 실행했더니..
왜 빈칸.. 빈박스....ㅜㅜ
알고 보니 kakao Maps API에서는 캡처 기능을 막아둔 것..
일단 포기하고 다른 기능부터 끝내놓고 마지막에 다시 찾아오기로 했다..
그리고 시간이 지나 이 부분을 해결해야하는 날이 왔다.
카카오 맵 자체를 캡처하지 못한다면..?
Map 위에 그려진 polyline을 캡쳐하면 되자나? > polyline은 Kako Maps Api에서 제공하는 Map 안에서만 그려지니 또 실패
그러다가 문득 생각이 들었다
Kakao에서 찍은 좌표값들을 가지고 다른 방식으로 그려주면 어떨까?
HTML에 그림을 그릴 수 있는 방법이 있다는 튜터님의 이야기를 듣고 찾아보니 canvas API가 있었다.
canvas API
간단하게 JS와 HTML을 사용하여 그래픽을 그릴 수 있는 HTML 기본제공하는 API
HTML에서 `<canvas>` 태그를 사용하여 작성하고 자바스크립트로 엘리먼트 인터페이스를 그려서 조작할 수 있다.
게다가 canvas의 그래픽 처리 성능은 상대적으로 좋은 편이고 비트맵을 활용한다고 한다. (비트맵 좋아하는 1인)
활용 방법
// Modal.jsx
<CanvasComponent size={200} coordinates={route.paths} />
부모 컴포넌트에서 route.paths 값을 props로 내려줌
여기서 route.paths는
{
"userId": "useruser",
"nickname": "이매동꼬부기",
...
"paths": [
{
"lat": 37.416791982311366,
"lng": 127.12909567647348
},
{
"lat": 37.41381877487271,
"lng": 127.12895502178257
},
{
"lat": 37.41395145417371,
"lng": 127.13120315418698
},
{
"lat": 37.41687967061569,
"lng": 127.13128733721635
},
{
"lat": 37.416791982311366,
"lng": 127.12909567647348
}
],
"totalDistance": 1049.1155725079668,
"totalWalkkTime": 15
}
이런식으로 설정이 되어있다. 여기서 `route.paths`를 보내주는 것
1. useRef로 캔버스 참조
const canvasRef = useRef(null);
useRef를 사용해 canvas 요소에 대한 참조를 생성
`canvas DOM` 요소를 직접 조작하는데 사용
2. useEffect로 캔버스 그리기 로직 구현
useEffect(() => {
coordinates나 size가 변경될 때마다 useEffect를 실행
3. 캔버스 컨텍스트와 크기 설정
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
canvas.width = canvasSize;
canvas.height = canvasSize;
`canvasRef.current`를 통해 캔버스 요소에 접근
2D 그래픽을 그리기 위해 getContext("2d")로 캔버스 컨텍스트를 가져옵
props.size 값을 사용해 캔버스의 크기를 설정
4. 캔버스 초기화
cctx.clearRect(0, 0, canvas.width, canvas.height);
5. 좌표를 그릴 공간 계산
const padding = 20;
const usableWidth = canvasSize - padding * 2;
const usableHeight = canvasSize - padding * 2;
캔버스 내부에서 실제로 경로를 그릴 영역을 정의
6. 좌표의 범위 계산
const latitudes = coordinates.map((coord) => coord.lat);
const longitudes = coordinates.map((coord) => coord.lng);
const minLat = Math.min(...latitudes);
const maxLat = Math.max(...latitudes);
const minLng = Math.min(...longitudes);
const maxLng = Math.max(...longitudes);
`coordinates 배열`에서 각각의 `위도`와 `경도`를 추출
최소값과 최대값을 계산 > 이 값으로 좌표의 범위를 구하고 캠버스에 맞게 변환
7. 좌표를 캔버스의 좌표계로 변환하고 경로 그리기
coordinates.forEach((coord, index) => {
const x = ((coord.lng - minLng) / (maxLng - minLng)) * usableWidth + padding;
const y = usableHeight - ((coord.lat - minLat) / (maxLat - minLat)) * usableHeight + padding;
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
각 경도를 minLng와 maxLng 사이의 비율로 변환 > 캔버스 너비에 맞춰 x 좌표 계산 (위도 포함)
첫 번째 좌표(index === 0)는 moveTo로 이동 > 이후의 좌표는 lineTo로 경로 연결
8. 스타일 설정 및 경로 그리기
ctx.strokeStyle = "#FF7B00";
ctx.lineWidth = 4;
ctx.stroke();
스타일 정의
9. JSX 반환
return <canvas ref={canvasRef} />;
그리고 결과적으로 리스트에 내가 그린 코스들이 나오게 되었다..
사실 위의 canvas API의 내용들이 완벽하게 이해가 되지는 않는다.
좌표를 구해서 캔버스의 비율안에 넣기는 봐도봐도 어렵고 머리속에서 잘 이해가 되지는 않지만..
일단 캡처 할 수 없는 상황 속에서 여러 고민 끝에 성공한 이 아이디어가 생각난 것에 큰 의미를 두고 싶다.