Canvas Animation

Franklee·2023년 3월 6일
0

normal

목록 보기
4/5
post-thumbnail

Canvas에 애니메이션 추가

이번 포스팅에서는 CanvasrequestAnimationFrame()을 사용해 애니메이션을 적용한 그림그리기 (Canvas 사용)을 알아보도록 하겠다.

requestAnimationFrame()

해당 메서드는 자바스크립트에서 반복적인 혹은 이번 포스팅의 내용과 같이 Canvas를 이용한 애니메이션 / 그리기에 사용 할 수 있다.

기본적인 구동원리는 반복문의 사용이다.

function animate(){
  requestAnimationFrame(animate);
  ...
}

requestAnimationFrame의 파라미터로 바깥 함수(animate)를 넣어주면 해당함수(animate)를 무한반복 하게 된다. requestAnimationFrame 하단에 작업내용을 작성해도 정상적으로 작동한다.

  • requestAnimationFrame 브라우저에게 수행하기를 원하는 애니메이션을 알리고 다음 리페인트가 진행되기 전에 해당 애니메이션을 업데이트하는 함수를 호출하게 합니다. 이 메소드는 리페인트 이전에 실행할 콜백을 인자로 받습니다.
  • 참고: 노트: 다음 리페인트에서 그 다음 프레임을 애니메이트하려면 콜백 루틴이 반드시 스스로 requestAnimationFrame()을 호출해야합니다.
  let xpoint = 0;
  function animate() {
    requestAnimationFrame(animate);
    const ctx = ref.current?.getContext('2d');
    if (ctx) {
      ctx.beginPath();
      ctx.arc(xpoint, 100, 30, 0, Math.PI * 2, false);
      ctx.strokeStyle = 'blue';
      ctx.stroke();
    }
    xpoint++;
  }

위 코드를 실행해 보면,


(이미지1) 의 형태가 나오게 된다.

여기서 ctx?.clearRect(x,y,w,h)ctx.beginPath();이전에 넣어주게되면 원통형 같은 모형이 아닌 단 하나의 원이 오른쪽으로 이동하는 것을 볼 수 있다.

이러한 방식을 이용해 그래프를 그려보려고 한다.

LineTo()를 사용한 그래프 애니메이션

원래 곡선을 그리려고 했지만 계산식이 많기에, 먼저 연습삼아 직선 그래프를 그려보려고 한다.

📝 문제

  • 포인트를 따라 그려지는 애니메이션 구현

🔍 이유

  • 입력 데이터에 따른 역동적인 UI를 제공하기 위함

💡 해결방안

먼저 (0,0)에서 (20,60)으로 그려지는 직선을 그려본다.

    let y = 0;

  const loopAnimate = () => {
    requestAnimationFrame(loopAnimate);
    const ctx = ref.current?.getContext('2d');
    if (ctx) {
      ctx?.clearRect(0, 0, 300, 200);
      ctx.beginPath();
      ctx.moveTo(0, 0);
      if (ctx !== undefined && ctx !== null) {
        ctx.strokeStyle = 'blue';
        ctx.lineWidth = 2;
      }

      ctx.lineTo(20, y);
      ctx.stroke();
      y++;
    }
  }

위 코드는 반복적으로 실행되기 때문에 y가 지속적으로 상승하지만 문제는


(이미지2) 와 같이 대각선이 변화하기 때문에 (0,0)부터 (20,60)까지 직선적으로의 움직임이 그려지지는 않는다.


이를 해결하기 위해 피타고라스의 정리를 이용해 애니메이션이 직선적으로 그려지도록 한다.

const pointY = Math.sqrt(to ** 2 + 20 ** 2) / 10; // 추가 계수

먼저 num ** e를 사용하여 num의 e제곱 을 계산하고, 루트메소드 Math.sqrt(num)를 사용하여 대각선의 길이를 계산한다. 그리고 이를 10으로 나눔으로 함수가 10번 실행되면 point에 도달 하도록 했다.


여기서 추가할 부분은 데이터에 대한 그리기가 다 끝났을 경우 requestAnimationFrame(loopAnimate);를 실행하지 않을 코드 추가한다.

    let y = 0;
	let bool = false;

  const loopAnimate = () => {
    if(!bool){
      requestAnimationFrame(loopAnimate);
    }
    ...//생략
      if(y>60){
        bool=true;/
      }
    }
  }

각 포인트 데이터를 그대로 사용하게 된다면, 60 => 20 으로 내려갈시 40의 높이 변화 차이가 있어야하는데 정작 실제로는 20의 높이 변화만의 차이가 생기게 된다.

즉, 이전 포인트와 현재 포인트의 차이 (ex. 60-20 = 40)의 계산을 통해 정상적인 그래프가 그려지도록 한다.

다만, 배열 첫 인덱스는 이전 데이터가 존재 하지 않기 때문에 처음만 제외 해주면 된다.

let to = point[i];

if (i > 0) {
   to = Math.abs(point[i - 1] - point[i]);
   //각 포인트 사이의 차이(y)를 계산 (하단 1번 붙임 참고)
 }
const pointY = Math.sqrt(to ** 2 + 20 ** 2) / 10; //to를 사용해 대각선 길이 도출

또한 상승 및 하강에 따라 + / - 를 구분 및 포인트에 도달시 i(인덱스)++

	if (dok === 'up') {
        // 상승
        y += pointY; // 계수 추가
        if (y >= point[i]) {
          //목표지점 도달시 => 다음 데이터 i 인덱스 ++
          dok = 'down';
          i++;
        }
      }

      if (dok === 'down') {
        // 하강
        y -= pointY; // 계수 빼기
        if (y <= point[i]) {
          //목표지점 도달시 => 다음 데이터 i 인덱스 ++
          dok = 'up';
          i++;
        }
      }

이제 그래프에 표시될 데이터가 들어있는 배열 point를 사용할 수 있는 형태로 함수를 수정한다.

  let x = 0;
  let y = 0;
  let i = 0;//인텍스
  let first = true;
  let dok = 'up';

  const loopAnimate = () => {
    if (i !== point.length) {
      //point배열의 인덱스가 길이와 같게 된다면 정지.
      requestAnimationFrame(loopAnimate);
    }
    const ctx = ref.current?.getContext('2d');
    if (ctx) {
      ctx?.clearRect(0, 0, 300, 200);
      if (first) {
        //함수가 반복되기 때문에 첫번째에 한번만 설정, 
        //이렇게 하지 않으면 모든 데이터 표시가 (0,0)으로 부터 시작되게 된다.
        ctx.beginPath();
        ctx.moveTo(0, 0);
        first = false;
      }
      if (ctx !== undefined && ctx !== null) {
        ctx.strokeStyle = 'blue';
        ctx.lineWidth = 2;
      }

      let to = point[i];

      if (i > 0) {
        to = Math.abs(point[i - 1] - point[i]);
        //각 포인트 사이의 차이(y)를 계산
      }

      const pointY = Math.sqrt(to ** 2 + 20 ** 2) / 10; 
      //각 포인트 to까지 대각선 길이 계산

      ctx?.lineTo(x, y);
      ctx?.stroke();

      x += 2;
      //데이터탕 넓이는 20, 위 Math.sqrt()에서 10으로 나눈 이유.

      if (dok === 'up') {
        // 상승
        y += pointY; // 계수 추가

        if (y >= point[i]) {
          //목표지점 도달시 => 다음 데이터 i 인덱스 ++
          dok = 'down';
          i++;
        }
      }

      if (dok === 'down') {
        // 하강
        y -= pointY; // 계수 빼기

        if (y <= point[i]) {
          //목표지점 도달시 => 다음 데이터 i 인덱스 ++
          dok = 'up';
          i++;
        }
      }
    }
  };

위 코드는 이전 포스트에서 잠깐 설명했던 seperatePoint를 사용하여 상승 / 하강지점의 데이터를 기반으로 사용이 가능한 코드이다.

profile
Frontend Dev

0개의 댓글