프론트엔드/React

카카오맵에 그린 도형 캡처하기 (html2canvas, canvas API)

장쫑이이 2024. 9. 23. 21:38

 

 

이번 프로젝트에서 개인적으로 가장 중요하게 생각했던 기능 중 하나는 `카카오 맵 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의 내용들이 완벽하게 이해가 되지는 않는다.

좌표를 구해서 캔버스의 비율안에 넣기는 봐도봐도 어렵고 머리속에서 잘 이해가 되지는 않지만..

 

일단 캡처 할 수 없는 상황 속에서 여러 고민 끝에 성공한 이 아이디어가 생각난 것에 큰 의미를 두고 싶다.