3-1-3.이미지 로드

이미지는 용량이 커서 로드 속도가 느리다. 웹은 이미지에 대해서 비동기적으로 동작하는데 완전히 로드될 때까지 기다리지 않고 웹 페이지를 일단 표시한 후 이미지는 따로 읽는다. 그래서 이미지를 읽은 직후 바로 출력하면 제대로 동작하지 않는다.

 

function draw() {

     var img = new Image();

     img.src="munjangdae.jpg"

     ctx.drawImage(img, 50, 0);

}

 

src에 파일 이름을 대입한다고 해서 즉시 이미지를 읽어 오는 것은 아니며 읽으라는 표시만 해 놓고 다음 명령을 수행한다. 그러다 보니 drawImage 메서드를 호출할 때는 아직 이미지가 다 읽히지 않은 상태이며 출력은 실패한다. 로컬에서 이 예제를 테스트하면 컴퓨터 속도가 워낙 빨라 성공하는 경우도 있지만 원격지의 웹 서버에서 받을 때는 실패할 확률이 높다.

그래서 이미지를 읽은 후 바로 출력하지 않고 이미지 로드가 완료된 직후인 onload 이벤트에 출력 코드를 작성하는 것이 정상적이다. 지금까지 우리는 이 방식으로 이미지를 출력했다. 그렇다면 이미지가 2개인 경우는 어떻게 할까? 둘 다 로드 완료되어야 출력 가능하므로 onload 이벤트를 중첩해야 한다.

 

imageload.html

function draw() {

     var img = new Image();

     img.src="munjangdae.jpg";

     img.onload = function() {

         ctx.drawImage(img, 50, 0);

        

         var img2 = new Image();

         img2.src="rose.png";

         img2.onload = function() {        

              ctx.drawImage(img2, 100, 0);     

         }

     }

}

 

문장대 위에 장미 이미지를 겹쳐서 출력했다. 문장대의 onload 에서 문장대를 먼저 출력하고 장미 이미지를 읽는다. 장미 이미지도 이벤트를 기다려야 하므로 역시 onload에서 출력한다.

둘 중 하나라도 로드되지 않으면 제대로 출력되지 않을 것이다. 그렇다면 이미지가 3개나 4개인 경우는 어떻게 될까? 마찬가지로 onload 이벤트 안에서 다른 이미지의 onload를 계속 기다려야 하므로 중첩이 너무 심해질 것이다. 게임의 경우 수백개의 이미지를 사용하는데 이런 식이라면 언제 게임을 시작하겠는가?

자바스크립트는 동기적인 이미지 로드 방법을 제공하지 않으며 컴파일된 실행 파일이 아니므로 실행 파일에 이미지를 내장할 수도 없다. 이벤트는 각각 따로여서 전부 성공했는지 일일이 체크하는 방법밖에 없다. 다음 예제는 이미지 로드를 대기하는 기초적인 방법을 보여 준다.

 

imageload2.html

<!DOCTYPE html>

<head>

     <meta charset="utf-8">

     <title>imageload2</title>

     <style>

         canvas {border:5px solid magenta;}

     </style>

</head>

<body>

     <canvas id="canvas" width="400" height="200">

         이 브라우저는 캔버스를 지원하지 않습니다.

     </canvas>

     <script>

         var canvas;

         var ctx;

         var loadcount = 0;

         var img = new Array(2);

         window.onload = function() {

              canvas = document.getElementById("canvas");

              if (canvas == null || canvas.getContext == null) return;

              ctx = canvas.getContext("2d");

              draw();

 

              img[0] = new Image();

              img[0].src="munjangdae.jpg";

              img[0].onload = function() {

                   loadcount++;

              }

              img[1] = new Image();

              img[1].src="rose.png";

              img[1].onload = function() {

                   loadcount++;

              }

             

              var timer = setInterval(function() {

                   if (loadcount == 2) {

                        clearInterval(timer);

                        draw();

                   }

              }, 100);

         }

        

         function draw() {

              if (loadcount != 2) {

                   ctx.font="30px arial";

                   ctx.fillText("로딩중...", 100, 100);

              } else {

                   ctx.drawImage(img[0], 50, 0);

                   ctx.drawImage(img[1], 100, 0);       

              }

         }

     </script>

</body>

</html>

 

onload에서 필요한 이미지를 로드하되 각 이미지의 onload 이벤트에서 성공한 개수를 카운트한다. 모두 성공했을 때의 이벤트는 따로 날라오지 않으므로 타이머를 돌려 가며 전부 성공했는지 조사하는 방법밖에 없다. 로드를 명령한 후 0.1초 간격으로 성공 개수인 loadcount 변수를 점검해 보고 이 값이 2가 될 때까지 계속 대기한다.

draw에서는 loadcount 2가 아니면 로딩중이라는 안내 메시지만 출력하고 아무 것도 하지 않는다. 필요한 이미지가 있어야 실행을 시작할 수 있다. 타이머에서 loadcount 2가 되면 새로 화면을 그리며 더 이상 대기할 필요가 없으므로 타이머는 제거한다. draw는 이때 비로소 이미지를 화면에 출력할 것이다.

위 예제는 이미지 로드를 대기하는 원론적인 방법을 보여 주지만 실제 사용하기에는 불편하다. 이 코드를 객체 지향적으로 잘 개선하여 편의성을 높여야 한다. 로드할 대상을 큐에 등록하고 타이머는 큐의 이미지를 하나씩 로드하며 로드가 성공할 때마다 프로그래스 바나 숫자 카운트를 보여 주어 사용자가 지루하지 않도록 해야 한다. 그 외에도 에러 처리와 실패시의 재시도 코드도 작성해야 한다.

인터프리터 언어인 자바스크립트에서 필요한 이미지를 완전히 로드하는 것은 쉽지 않다. 실제로 게임 이미지 중 하나가 누락되어 시작을 못하는 경우도 종종 있다. 여러 가지 다른 해결책도 있는데 다음 예제는 코드에서 실시간으로 이미지를 읽어 오는 대신 HTML 페이지에 살짝 숨겨 놓은 이미지를 활용한다.

 

imageload3.html

<!DOCTYPE html>

<head>

     <meta charset="utf-8">

     <title>imageload3</title>

     <style>

         canvas {border:5px solid magenta;}

     </style>

</head>

<body>

     <canvas id="canvas" width="400" height="200">

         이 브라우저는 캔버스를 지원하지 않습니다.

     </canvas>

     <div style="display:none">

         <img id="munjangdae" src="munjangdae.jpg">

     </div>

     <script>

         var canvas;

         var ctx;

         window.onload = function() {

              canvas = document.getElementById("canvas");

              if (canvas == null || canvas.getContext == null) return;

              ctx = canvas.getContext("2d");

              draw();

         }

        

         function draw() {

              var img = document.getElementById("munjangdae");

              ctx.drawImage(img, 50, 0);

         }

     </script>

</body>

</html>

 

body안에 div 태그를 두고 이 안에 이미지를 배치하되 이 이미지는 실행중에 캔버스에서 사용할 것이므로 페이지에 나타나서는 안된다. div display 속성을 none으로 지정하여 숨긴다. 또는 hidden 공통 속성으로 숨길 수도 있다. 그러나 visibility 속성으로 hidden으로 설정하는 방법은 이미지 크기만큼 스크롤 영역이 계산되므로 좋지 않다.

숨겨진 디비전안에 이미지를 배치해 놓으면 HTML 문서를 읽을 때 브라우저가 이미지를 로드할 것이다. 따라서 window.onload 이벤트가 발생했을 때는 이미 이미지가 로드된 상황이며 이벤트를 기다리지 않아도 바로 사용할 수 있다. 스크립트 코드에서 숨겨진 이미지를 참조해야 하므로 id를 반드시 주어야 한다. 코드에서는 documentgetElementById 메서드로 이미지를 읽어 사용한다.