자바스크립트로 애니메이션 구현 및 2D 성능 측정

안녕하세요. 이번 시간에는 자바스크립트를 활용하여 현재 보고 있는 웹브라우저 화면에 각각 독립적으로 움직이는 공 애니메이션 효과를 만들 거예요. 공을 그리기 위해 HTML5의 기능 중 하나인 Canvas를 사용할 건데요. Canvas는 동적인 그래픽을 그리기 위해 사용됩니다. 즉 2D 그래픽이나 애니메이션 효과를 웹 페이지에 하나도 안 쉽게 표현할 수 있어요.

 

웹브라우저에서 굴러다니는 공으로 대략적인 2D 그래픽 성능도 확인해 볼 수 있는데요. 시간이 지남에 따라 공의 개수가 많아지도록 해서 실시간으로 프레임을 측정하고 공이 최대 몇 개까지 안정적인 프레임을 유지하는지 확인해 보는 거죠! canvas는 그래픽카드의 하드웨어 가속을 사용하여 2D 그래픽을 렌더링 하기 때문에 가능한 거예요. 하지만 성능 테스트 결과는 기기나 브라우저, 운영체제 등 많은 요소에 의해 영향을 받기 때문에 정확하지 않을 수 있어서 그냥 재미로 해보는 게 좋을 거 같네요!

 

자 이제 사용하시는 브라우저에서 콘솔 창으로 들어갑니다. (이제 설명 안 해도 어디서 코드를 입력하고 실행하는지 아실 거예요.)

 

화면은 빈 페이지가 아니더라도 어디서든 가능하니, 저는 제 개인 홈페이지에서 하도록 할게요. 빈 페이지에 하고 싶으신 분들은 인터넷 옵션에서 시작 페이지를 about:blank로 설정하면 아무것도 없는 깨끗한 화면이 나옵니다.

 

먼저 현재 보고 있는 화면 가운데에 공을 그려줄게요, 공은 다른 요소들보다 맨 앞쪽에 위치하도록 하겠습니다.

// Canvas 생성(현재화면 위에 공을 그릴 캔버스를 위에 올리는것) 
const canvas = document.createElement('canvas');
canvas.style.position = 'fixed'; // 고정 위치
canvas.style.top = '0'; // 캔버스를 화면 위쪽에 위치
canvas.style.left = '0'; //캔버스를 화면 왼쪽에 위치
canvas.style.zIndex = 9999; //맨 앞단에 보이게 
canvas.width = window.innerWidth; // 화면 너비를 현재 브라우저 창에 맞게
canvas.height = window.innerHeight; // 화면 높이를 현재 브라우저 창에 맞게
document.documentElement.appendChild(canvas); // Canvas를 현재 화면에 추가
const ctx = canvas.getContext('2d'); // CanvasRenderingContext2D 객체 생성
// 공 정의
class Ball {
    constructor(x, y, xSpeed, ySpeed) {
        this.x = x; //공의 x좌표
        this.y = y //공의 y좌표
        this.xSpeed = xSpeed; //공의 x좌표 속도
        this.ySpeed = ySpeed; //공의 y좌표 속도
    }
}
// 공 그리기
function drawBall(ball) {
    ctx.beginPath();  // 도형 시작
    ctx.arc(ball.x, ball.y, 20, 0, Math.PI * 2); // x좌표, y좌표, 반지름(크기),각도 0도 ~ 각도 360도 (원 모양)
    ctx.fillStyle = 'green';  // 채우기 스타일 설정
    ctx.fill();  // 도형 채우기
}
//공의 위치 x,y좌표만 넣고 실행  
let x = canvas.width / 2;  //현재 화면의 x축 중앙
let y = canvas.height / 2; //현재 화면의 y축 중앙
ball = new Ball(x,y);  //공을 생성
drawBall(ball); //화면에 공을 그린다

 

실행 화면 ▼

애니메이션 실행화면

 

초록색 공이 만들어졌네요! 이제 공을 계속 움직이게 하는 requestAnimationFrame을 사용해서 animate() 함수를 만들게요.

const balls = []; // 공 배열 생성
let ballCount = 0; //공의 숫자 

//움직이는 공을 추가하는 함수
function addBall() {
    const x = Math.random() * (canvas.width - 40) + 20;  //공의 X좌표 랜덤
    const y = Math.random() * (canvas.height - 40) + 20; //공의 y좌표 랜덤
    const xSpeed = Math.random() * 4 - 2; //공의 X좌표 속도 랜덤
    const ySpeed = Math.random() * 4 - 2; //공의 y좌표 속도 랜덤
    const newBall = new Ball(x, y, xSpeed, ySpeed); //공 생성 
    balls.push(newBall); //공 배열에 넣어준다 
    ballCount++; //공의 개수
}
// 공을 움직는 함수
function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 공을 지워준다

    // 모든 공들에 대해서 좌표를 업데이트하고 경계를 넘어갔다면 반대방향으로 이동    
    for (const ball of balls) {
        ball.x += ball.xSpeed;
        ball.y += ball.ySpeed;
        if (ball.x + 20 > canvas.width || ball.x - 20 < 0) {
            ball.xSpeed = -ball.xSpeed;
        }
        if (ball.y + 20 > canvas.height || ball.y - 20 < 0) {
            ball.ySpeed = -ball.ySpeed;
        }
        drawBall(ball); // 계산된 좌표로 공을 다시 그린다
    }
    // 다음 프레임을 실행 
    requestAnimationFrame(animate);
}
// 브라우저 창 크기 변경 시 Canvas 크기 조정
window.onresize = function() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}
addBall(); //움직이는 공 추가
animate(); // 애니메이션 시작

 

새로운 공을 추가하는 addBall() 함수 먼저 실행하고, 공을 움직이는 animate() 함수를 실행할게요. 잘 굴러다니는지 볼까요?

실행 화면 ▼

공굴리기1
실제로는 훨씬 부드러움..

 

현재 브라우저 창 크게에 맞게 잘 굴러다니네요! 지금은 addBall() 함수를 직접 실행해야 공이 계속 늘어나게 되는데요. 이제 직접 공을 추가하지 않고 자동으로 공이 늘어나도록 만들고, 공의 개수를 확인할 수 있게 화면에 표시할게요. 공이 자동으로 늘어날 수 있도록 타이머 설정을 하는 setInterval () 함수를 사용하고 화면에 표시할 수 있는 drawBallCount() 함수를 생성합니다.

// 공 개수 표시
function drawBallCount(count) {
    ctx.font = '24px Arial'; //폰트 지정
    ctx.fillStyle = 'green'; //글자 색상
    ctx.fillText(`공 개수: ${count}`, 10, 30); // 문자열을 (10,30)좌표에 위치 
}

// 공을 움직는 함수
function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 공을 지워준다

    // 모든 공들에 대해서 좌표를 업데이트하고 경계를 넘어갔다면 반대방향으로 이동
    for (const ball of balls) { 
        ball.x += ball.xSpeed;
        ball.y += ball.ySpeed;
        if (ball.x + 20 > canvas.width || ball.x - 20 < 0) {
            ball.xSpeed = -ball.xSpeed;
        }
        if (ball.y + 20 > canvas.height || ball.y - 20 < 0) {
            ball.ySpeed = -ball.ySpeed;
        }
        drawBall(ball); // 계산된 좌표로 공을 다시 그려준다
    }
    drawBallCount(ballCount); //공의 개수 갱신
    requestAnimationFrame(animate); // 다음 프레임을 실행 
}
// 브라우저 창 크기 변경 시 Canvas 크기 조정
window.onresize = function() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}

// 공 추가 타이머 설정
const intervalID = setInterval(addBall, 100); // 0.1초마다 공 추가

// 애니메이션 시작
animate();

 

실행 화면 ▼

공굴리기2
개수 제한이 없으니 조심

 

공을 0.1초마다 설정을 해서 빠르게 늘어나고 있네요! setInterval(addBall, 100) 이 함수에서 첫 번째 인자는 반복 실행할 함수이고, 두 번째는 실행 간격을 밀리 초 단위로 지정하는 거예요. 1000밀리 초는 1초에 해당합니다. setInterval()를 실행 시 고유 ID를 반환하는데요, 그 ID를 사용해서 중지할 수 있으니 너무 많이 늘어난다면 clearInterval(intervalID)을 실행해서 멈춰 주세요.

 

이제 마지막으로 2D 렌더링 성능을 측정할 수 있도록 현재 화면에 현재 프레임 값을 표시할게요. 기존 drawBallCount() 함수에 frameRate 값을 추가로 받아 표시하고, animate() 함수에 프레임 계산하는 코드를 추가해 줍니다. 아래 화면에 lastFrameTime 값과 frameRate 초기화 값도 꼭 입력해 주세요~ 마지막으로 제 컴퓨터의 2D 성능을 테스트를 해보겠습니다!

let lastFrameTime = performance.now(); //현재 시간을 밀리초 반환
let frameRate = 0; //프레임 값

// 공 개수 표시
function drawBallCount(count, frameRate) {
    ctx.font = '24px Arial'; //폰트 지정
    ctx.fillStyle = 'green'; //글자 색상
    ctx.fillText(`공 개수: ${count}`, 10, 30); // 문자열을 (10, 30) 좌표에 위치
    ctx.fillText(`Frame rate: ${frameRate.toFixed(2)} FPS`, 10, 60); // 문자열을 (10,60)좌표에 위치 
}

//animate 호출시 currentFrameTime값 추가
function animate(currentFrameTime) {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 공을 지워준다

    // 프레임 레이트 계산 추가
    const deltaTime = currentFrameTime - lastFrameTime;
    frameRate = 1000 / deltaTime;
    lastFrameTime = currentFrameTime;

    // 모든 공들에 대해서 좌표를 업데이트하고 경계를 넘어갔다면 반대방향으로 이동
    for (const ball of balls) {
        ball.x += ball.xSpeed;
        ball.y += ball.ySpeed;
        if (ball.x + 20 > canvas.width || ball.x - 20 < 0) {
            ball.xSpeed = -ball.xSpeed;
        }
        if (ball.y + 20 > canvas.height || ball.y - 20 < 0) {
            ball.ySpeed = -ball.ySpeed;
        }
        drawBall(ball); // 계산된 좌표로 공을 다시 그린다
    }
    drawBallCount(ballCount, frameRate); // 공의 개수와 프레임값을 갱신
    requestAnimationFrame(animate); // 다음 프레임을 실행
}

 

실행 화면 ▼

공굴리기3
조금 징그러운..

 

140프레임에서 시작했지만 공이 7천 개가 되니 70 프레임으로 떨어졌어요! 사실 70 프레임만 돼도 공들이 부드럽게 움직이지만 제 PC의 팬 돌아가는 소리가 너무 커지고 화면의 동영상 찍는 프로그램이 갑자기 메모리 용량 초과로 멈췄네요. 더 이상 측정이 불가능해서 브라우저 창을 닫았습니다. 다음 기회에 다시 도전해 볼게요!

 

 

오늘은 이렇게 자바스크립트와 HTML5 캔버스를 사용하여 공이 움직이는 애니메이션을 구현하고, 공의 개수와 프레임 레이트를 실시간으로 표시하는 예제를 살펴보았습니다. 테스트를 통해 브라우저의 2D 렌더링 성능을 쉽게 평가할 수 있었는데요. 이 예제는 단순한 테스트이기 때문에 재미로만 봐주시길 바랄게요! - 끝