프로젝트 리팩토링 - 문제와 문제 해결편 [1]

무제·2021년 9월 2일
0

프로젝트

목록 보기
2/3
post-thumbnail

🧑🏻‍🔧 코드를 엎다.

기존 프로젝트에는 그림판 기능이 존재했다. 그러나 코드가 과하게 많고 길다는 점이 만들 때부터 마음에 걸렸지만 그 때는 사소한 문제까지 신경을 쓸 여유가 없었다. 그래서 리팩토링을 하기로 마음 먹었었고, 개선해야 할 부분을 찾아 보기로 했다.

그러나, 부분적으로 고쳐 버리면 사슬처럼 너무 많은 버그가 생기는 문제점이 발생했다. 뭔가 파도 파도 끝없이 나오는 고구마를 캐는 기분이였다. 게다가, 기본적으로 코드가 500, 600줄을 넘는 컴포넌트가 꽤나 있어, 위로 올렸다 내렸다 보는 것도 고통의 연속이였다.

그래서 결심했다. 그냥 다시 처음부터 해버리자고 👊

🎨 Canvas를 다루면서 생겨난 문제

그림판 기능을 사용할 수 있는 라이브러리는 존재했다. 그러나 과감히 포기했다. 편하게 만들 수 있었겠지만 시간적 여유도 있고, canvas api를 공부해보고 싶었다. 리팩토링을 진행하면서 몇 가지의 문제점이 닥쳤다.

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

1. canvas에서 몇 가지의 기능을 사용하려면 CanvasRenderingContext2D 라는 인터페이스에 접근해야 하는데, 위 처럼 vanila JS 에서는 getContext 객체를 변수에 담아서 접근할 수 있었는데, 리액트에선 useRef로 canvas DOM에 다이렉트로 접근 시, 렌더링 시마다 null 오류가 생기는 문제.

2. canvas를 resizing 하면 마우스 커서로 찍는 좌표와 캔버스 위에 그려지는 좌표가 달라지는 문제.

🔧 해결 방법

첫 번째 문제는 React-Hooks인 useEffect를 사용하여 해결했다.

const canvasRef = useRef(null);
const [ctx, setCtx] = useState();
const [canvasCur, setCanvasCur] = useState();

useEffect(() => {
  const canvas = canvasRef.current;
  const context = canvas.getContext("2d");
  ...
  ...
  setCanvasCur(canvas);
  setCtx(context);
}, []);

dependency 자리에 빈 배열을 넣으면서, 첫 렌더링 시에만 작동하게 만들고 그 안에서 canvas의 context를 컴포넌트의 useState를 사용하여 어디서든 접근 할 수 있게 해주었다.

어찌보면 간단한 문제지만, 오래 생각해 보다가 구글링을 해결했다. 다른 방법도 찾았는데 useState로 context를 관리 하는 것이 아니라, useRef로 관리하는 방법도 있었다.

const ctx = useRef();

useEffect(() => {
  const canvas = canvasRef.current;
  const context = canvas.getContext("2d");
  ...
  ...
  ctx.current = context;
}, [])

두 번째 문제는 해결하는 데 오랜 시간이 걸렸다. 구글링으로도 찾기 어려웠다. canvas Element의 width, heigth 과 css에서 접근하는 canvas의 width, height가 다르다는 점도 헷갈리고 어려웠다.

The canvas element has two attributes to control the size of the element's bitmap: width and height. These attributes, when specified, must have values that are valid non-negative integers. The rules for parsing non-negative integers must be used to obtain their numeric values. If an attribute is missing, or if parsing its value returns an error, then the default value must be used instead. The width attribute defaults to 300, and the height attribute defaults to 150.

<canvas width="150" height="100"></canvas>

즉, canvas는 필수적으로 width 값과 height 속성 값을 가져야 하는데 이건 css에서 접근하는 width, height이 아닌 위 처럼 html 요소의 속성을 의미한다. 만약 두 속성을 지정해주지 않으면 기본값으로 300 * 150의 canvas를 렌더한다.

const getMouesPosition = (nativeEvent, canvas) => {
  let mouseX = (nativeEvent.offsetX * canvas.width) / canvas.clientWidth;
  let mouseY = (nativeEvent.offsetY * canvas.height) / canvas.clientHeight;
  return { x: mouseX, y: mouseY };
};

위의 로직으로 문제를 해결 했다.
canvas의 left와 top을 기준으로 마우스로 찍고 있는 곳을 리턴하는 offsetX, offsetY,
고정으로 정해놓은 canvas 요소의 width, height,
리사이징 된 캔버스의 clientWidth, clientHeight

따라서 canvas 안에서 마우스로 찍은 좌표 값에 고정 크기를 곱하고 리사이징 된 현재의 캔버스의 크기를 나눠주면 항상 고정으로 정해놓은 canvas 요소의 width, height를 기준으로 상대 값이 나온다.

이 두 값을 사용하여 마우스 커서로 찍는 좌표와 캔버스 위에 그려지는 좌표가 달라지는 문제를 해결하였다.

📎 참조

HTML Living Standard — Last Updated 1 September 2021

profile
표현할 수 없는 무제공책

0개의 댓글