Web 2.0이 탄생시킨 화두가 있다면 Asynchronous JavaScript and XML (Ajax)일 것이다. Google Maps™ 매핑 서비스와 Gmail™ 웹 메일 서비스 같은 애플리케이션에서의 클라이언트 측 인터랙팅은 Ajax를 흥미롭고 유용하게 만드는 요소이다. Hypertext Markup Language (HTML), Java-Script coding, Cascading Style Sheets (CSS), XML, 비동기식 웹 요청 같은 Ajax의 기술들로 Web V1.0 보다 더 활발한 웹 인터랙션을 만들 수 있다. 물론, 이러한 기술들은 Microsoft Internet Explorer V4부터 존재했고 최근에는 다른 고급 애플리케이션도 이와 비슷한 기능을 선보이고 있다.
Ajax를 구현하기가 얼마나 까다로울까? Ajax 모델의 각 엘리먼트는 비교적 배우기 쉽다. 하지만 문제는 이 모든 엘리먼트를 어떻게 섞느냐에 달려있다. 다양한 사람들이 클라이언트 측 코딩과 서버 측 코딩을 수행하기 때문에 문제는 복합적이다. 이 글에서는 어떻게 단 한 사람이 작은 Ajax 기반 슬라이드 뷰잉 애플리케이션을 두어 시간 안에 작성할 수 있는지를 보여주겠다.
Macintosh 상의 Apple iPhoto 같은 이미지 관리 애플리케이션은 슬라이드 쇼를 대중화 했다. 슬라이드 쇼에서 이미지는 시간 순서대로 나타나고 이미지가 나타났다가 사라진다. 게다가 이미지는 "Ken Burns Effect"효과를 사용하여 이동한다.
이 예제에서 브라우저를 사용하여 서버에서 이미지 리스트를 다운로드 한다. 그런 다음 그 이미지 리스트를 사용하여 Dynamic HTML (DHTML)을 사용하는 슬라이드 쇼를 구성한다. Ken Burns 효과를 사용하여 불규칙한 느린 움직임, 줌, 페이드(fade)로 이미지를 동화시켜 Macromedia Flash나 다른 애니메이션 툴을 다운로드 하지 않아도 된다.
Ajax가 무엇이 다른지 이해하려면 현재 웹 프로그래밍 모델을 이해해야 한다. 클라이언트와 서버 간 인터랙션이 그림 1에 나타나있다.
그림 1. Web V1.0의 클라이언트-서버 인터랙션 모델
웹 브라우저 또는 클라이언트는 웹 서버에서 GET
또는 POST
요청을
만든다. 서버는 HTML 응답을 포맷팅 한다. 클라이언트는 HTML을 파싱하고 이것을 사용자에게 디스플레이 한다. 사용자가 또
다른 링크나 버튼을 클릭하면 또 다른 요청이 서버에 만들어지고 현재 페이지는 서버가 리턴하는 새로운 페이지로 대체된다.
새로운 모델은 보다 비동기식이다.(그림 2)
그림 2. Ajax 클라이언트-서버 인터랙션 모델
이 새로운 모델에서, 서버는 전과 같이 HTML 페이지를 리턴한다. 하지만 이 새로운 페이지에는 Java-Script가
있다. 이 코드는 필요한 추가 정보를 서버에 호출한다. 그러한 요청들은 Representational State Transfer
(REST) 서비스에 대한 GET
요청으로서 만들어 질 수 있고 또는 SOAP에 필요한 POST
요청으로서 만들어 질 수 있다.
JavaScript 코드는 응답을 파싱하고, 종종 XML로 인코딩 되고, 페이지 상에 HTML을 업데이트 하여 새로운 데이터를 반영한다. XML 외에도 엔지니어는 Java-Script Serialized Object Notation (JSON) 포맷으로 인코딩 된 데이터를 리턴한다. 이 데이터는 다른 클라이언트 유형 보다 브라우저가 이해하기 더 쉽다. 리턴 되는 XML의 값은 브라우저 외의 클라이언트가 데이터를 인터프리팅 하는 것이다. 선택은 여러분과 애플리케이션에 달려있다.
Ajax 슬라이드 쇼를 개발하는 첫 번째 단계는 REST 데이터 서비스를 한데 모으는 것이다. 이 예제에서, 모든 슬라이드 쇼 이미지와 모든 사이즈(넓이와 높이)를 리턴하는 PHP 페이지를 사용한다. 모든 이미지들은 images 라고 하는 디렉토리에 있다. 이 파일의 이름은 name_width_height.jpg 형식을 띤다. 예를 들어, oso1_768_700.jpg는 나의 강아지들 중 하나인 Oso의 그림이고, 넓이 768 픽셀에 높이 700 픽셀이라는 의미이다. Adobe PhotoShop 이나 Macromedia Fireworks를 열지 않고도 이미지의 넓이와 높이를 볼 수 있기 때문에 이러한 유형의 네이밍을 선호한다.
리스트를 제공하기 위해 PHP 서버 코드를 사용한다.(Listing 1)
Listing 1. slides.php 서버 페이지
<?php header( "Content-type: text/xml" ); ?> <slides> <?php if ($handle = opendir('images')) { while (false !== ($file = readdir($handle))) { if ( preg_match( "/[.]jpg$/", $file ) ) { preg_match( "/_(\d+)_(\d+)[.]/", $file, $found ); ?> <slide src="images/<?php echo $file; ?>" width="<?php echo $found[1]; ?>" height="<?php echo $found[2]; ?>" /><?php echo( "\n" ); ?> <?php } } closedir($handle); } ?> </slides> |
이 코드는 비교적 간단하다. 시작하기 위해 콘텐트 유형을 XML로 설정한다. 브라우저가 문서를 XML로 인식하고 이에 대한 문서 객체 모델(DOM)을 만드는 것이 중요하다. 이 코드는 <slides>
태그를 시작하고 이미지 디렉토리를 읽어서 각 이미지에 대한 <slides>
태그를 만든다. 마지막으로 이 스크립트는 <slides>
태그를 닫는다.
kenburns라고 하는 디렉토리에서 나의 로컬 호스트에 호스팅 된 Mozilla Firefox 브라우저에서 페이지를 검색하면 다음과 같은 결과를 보게 된다.(그림 3)
그림 3. slides.php 서버 스크립트의 아웃풋
세 개의 이미지들이 있다. 한 개는 내 딸 사진이고 두 개는 강아지 사진이다. 여기에 디테일과 멀티미디어 등 원하는 것을 추가할 수 있지만 이 예제를 위해서 단순히 유지하려고 한다.
다음 단계는 서비스에서 데이터를 읽고 브라우저와 서버 간 Ajax 연결이 작동한다는 것을 확인 할 HTML 페이지를 작성하는 단계이다.(Listing 2) 이 HTML 코드와 삽입된 JavaScript 코드는 XML을 가져와서 서버가 리턴하는 텍스트에 나타난 경고를 가져온다.
Listing 2. Ajax 가져오기 페이지
<html> <body> <script> function processReqChange() { if (req.readyState == 4 && req.status == 200 && req.responseXML != null) { alert( req.responseText ); } } function loadXMLDoc( url ) { req = false; if(window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch(e) { req = false; } } else if(window.ActiveXObject) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { req = false; } } } if(req) { req.onreadystatechange = processReqChange; req.open("GET", url, true); req.send(""); } } loadXMLDoc( "http://localhost/kenburns/slides.php" ); </script> </body> </html> |
이 코드는 지정된 URL에서 XML 콘텐트를 파악하고 loadXMLDoc
함수는 Ajax 요청을 시작한다. 이 요청은 비동기식으로 실행되어 페이지를 검색하고 결과를 리턴한다. 요청이 완료되면 processReqChange
함수가 결과와 함께 호출된다. 이 경우 processReqChange
함수는 경고창에 responseText
함수의 값을 디스플레이 한다. Firefox 브라우저에서 이 페이지를 실행하면 그림 4처럼 된다.
그림 4. 경고창에 나타난 XML
출발이 좋다. 확실히 서버에서 XML 데이터를 가져왔다. 하지만 몇 가지만 짚고 넘어가자. 먼저, URL은 절대 경로이자, 도메인 이름이며, 모든 것이다. 이것은 Ajax에 유일하게 쓰일 수 있는 URL 스타일이다. Ajax JavaScript 코드를 작성하는 서버 코드는 유효한 완전한 형식을 갖춘 URL을 만든다.
여기에서 분명하지 않은 또 한가지는 Ajax 보안 주의 사항이다. JavaScript 코드는 어떤 URL도 요청할 수 없다. URL은 이 페이지와 같은 도메인 이름을 가져야 한다. 이 경우 이것은 로컬호스트이다. 하지만 www.mycompany.com에서 HTML을 렌더링 할 수 없고 data.mycompany.com에서 스크립트가 데이터를 검색하도록 할 수 있다. 두 도메인 모두 정확히 맞아야 한다. 하위 도메인까지 말이다.
요청 객체를 만들기 위해 백플립을 수행하는 것처럼 보이는 loadXMLDoc
의 코드를 주목하라. 왜 이렇게 어지러운가? Pre-version 7 Internet Explorer에는 XMLHTTPRequest
객체 유형이 빌트인 되어있지 않다. 따라서 Microsoft ActiveX 제어를 사용해야 한다.
마지막으로 processReqChange
함수에서 readyState
가 4와 같고, status
가 200으로 설정된 것을 찾았다. readyState
값이 4라는 것은 트랜잭션이 완성되었다는 의미이다. status
값이 200이란 것은 페이지가 유효하다는 의미이다. 페이지를 찾을 수 없을 경우 에러 메시지 404가 생긴다. 여기에서 예외 경우를 다루지 않는다. 이것은 단순한 예제 코드에 불과하기 때문이다. 하지만 Ajax 코드는 에러를 리턴하는 요청을 핸들 해야 한다.
슬라이드 쇼를 만드는 방법을 설명하기 전에, 현재 예제를 확장해 보겠다. processReqChange
함수를 사용하여 XML 요청의 결과를 갖고 있는 HTML 테이블을 만들 것이다. XML을 읽을 수 있고 여기에서 HTML을 동적으로 만들 수 있는지를 테스트 할 수 있다.
Listing 3은 리턴된 XML에서 테이블을 만드는 코드 모습이다.
Listing 3. 강화된 테스트 페이지
<html> <body> <table> <tbody id="dataTable"> </tbody> </table> <script> function processReqChange() { if (req.readyState == 4 && req.status == 200 && req.responseXML != null) { var dto = document.getElementById( 'dataTable' ); var items = []; var nl = req.responseXML.getElementsByTagName( 'slide' ); for( var i = 0; i < nl.length; i++ ) { var nli = nl.item( i ); var src = nli.getAttribute( 'src' ).toString(); var width = parseInt( nli.getAttribute( 'width' ).toString() ); var height = parseInt( nli.getAttribute( 'height' ).toString() ); var trNode = document.createElement( 'tr' ); var srcNode = document.createElement( 'td' ); srcNode.innerHTML = src; trNode.appendChild( srcNode ); var widthNode = document.createElement( 'td' ); widthNode.innerHTML = width.toString(); trNode.appendChild( widthNode ); var heightNode = document.createElement( 'td' ); heightNode.innerHTML = height.toString(); trNode.appendChild( heightNode ); dto.appendChild( trNode ); } load_slides( items ); start_slides(); } } function loadXMLDoc( url ) { req = false; if(window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch(e) { req = false; } } else if(window.ActiveXObject) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { req = false; } } } if(req) { req.onreadystatechange = processReqChange; req.open("GET", url, true); req.send(""); } } loadXMLDoc( "http://localhost/kenburns/slides.php" ); </script> </body> </html> |
이 페이지를 브라우저에 로딩하면 그림 5와 같이 된다.
그림 5. 업데이트 된 테스트 페이지
업데이트 된 processReqChange
코드는 이제 responseText
텍스트 대신 responseXML
객체를 본다. 게다가 getElementsByTagName
을 사용하여 모든 <slide>
태그에 액세스 한다. 여기에서부터 src
, width
, height
애트리뷰트를 파싱하고 document
객체에 createElement
메소드를 사용하여 데이터를 보유할 열과 셀을 만든다. createElement
메소드를 사용하는 방식은 이전 방식보다 더 강력하다. 테이블의 내용을 가진 HTML 스트링을 만들 수 있고 innerHTML
을 사용하여 데이터를 기존 엘리먼트에 추가할 수 있기 때문이다.
슬라이드 쇼에서 이미지들을 구분하는 웹 서비스가 있으니 그러한 슬라이드를 디스플레이 하고 Ken Burns Effect 애니메이션을 수행하는 클라이언트 코드가 필요하다. 이를 위해 세 가지 기본적인 기능을 수행하는 JavaScript 객체를 모아야 한다.
- 이미지 캡슐화
- 기본 애니메이션 엔진 제공
- 효과주기 (움직임, 줌, 페이드)
이미지 컨테이너부터 시작하여 ImageInfo
라는 클래스를 만든다.(Listing 4)
Listing 4. ImageInfo.js
function ImageInfo( src, width, height, htmlObj ) { this.src = src; this.width = width; this.height = height; this.current_width = width; this.current_height = height; this.htmlObj = htmlObj; this.htmlObj.src = this.src; this.htmlObj.width = this.current_width; this.htmlObj.height = this.current_height; } ImageInfo.prototype.set_opacity = function( opacity ) { this.htmlObj.style.MozOpacity = opacity / 100; var f = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+opacity+')'; this.htmlObj.style.filter = f; } ImageInfo.prototype.set_position = function( x, y ) { this.htmlObj.style.left = x+'px'; this.htmlObj.style.top = y+'px'; } ImageInfo.prototype.set_size = function( w, h ) { this.current_width = w; this.current_height = h; this.htmlObj.width = this.current_width; this.htmlObj.height = this.current_height; } ImageInfo.prototype.get_image = function() { return this.htmlObj; } ImageInfo.prototype.hide = function() { this.htmlObj.style.visibility = 'hidden'; } ImageInfo.prototype.show = function() { this.htmlObj.style.visibility = 'visible'; } |
슬라이드 쇼에 있는 모든 이미지의 경우 하나의 상응하는 ImageInfo
객체가 있다. 이 객체는 이미지에 대해 알려진 정보를 캡슐화 한다.(src
, width
, height
) 또한 문서에 이미지를 디스플레이 하는 HTML <img>
태그에 대한 레퍼런스도 갖고 있고 도움말 메소드도 있어서 이미지를 움직이고, 투명도를 설정하기도 한다. Firefox 와 기타
Gecko 기반 브라우저에서 MozOpacity 스타일은 투명도를 설정하는데 사용된다. Internet Explorer에서는 필터
효과가 사용된다.
간단한 애니메이션 엔진을 만든다. Animation.js 파일에 있는 코드는 다음과 같다.(Listing 5)
Listing 5: Animation.js
function Animation( am, img, seconds, effects ) { this.img = img; this.animationManager = am; this.seconds = seconds; this.effects = effects; this.startMS = 0; } Animation.prototype.start = function() { this.animationManager.add( this ); this.startMS = 0; this.img.hide(); for( var e in this.effects ) { this.effects[e].apply( 0 ); } this.img.show(); } Animation.prototype.animate = function() { var d = new Date(); if ( this.startMS == 0 ) this.startMS = d.valueOf(); var p = (((d.valueOf()-this.startMS)/1000)/this.seconds)*100; for( var e in this.effects ) this.effects[e].apply( p ); } Animation.prototype.done = function() { var d = new Date(); return ( ( d.valueOf() - this.startMS ) / 1000 ) > this.seconds; } function AnimationManager( speed ) { this.animations = []; var self = this; window.setInterval( function() { self.idle(); }, speed ); } AnimationManager.prototype.add = function( anim ) { this.animations.push( anim ); } AnimationManager.prototype.idle = function() { if ( this.animations.length > 0 ) { this.animations[0].animate(); if ( this.animations[0].done() ) this.animations.shift(); if ( this.animations.length == 0 ) this.on_finished(); } } AnimationManager.prototype.on_finished = function() { } |
Listing 5는 두 개의 클래스, Animation
과 AnimationManager
를 보여주고 있다. AnimationManager
클래스는 타이머를 제어하고 움직임 메시지를 Animation
객체의 리스트에 있는 첫 번째 아이템에 보낸다. Animation
객체가 완료되었다는 것을 알려주면 클래스는 다음 아이템으로 옮겨간다.
Animation
객체는 지정된 시간에 초 단위로 특정 기간 동안 특정 이미지에 효과를 적용한다. 이는 Animation
객체가 퍼센트 완전한 메시지를 계산하고 이를 각 효과의 apply
메소드로 보내는 것이다. 그 효과는 백분율에 기반하여 그 이미지에 수행해야 하는 것을 계산한다. 예를 들어, 움직임 효과는 시작
포인트와 끝 포인트를 알고 있고 이것은 백분율에 기반하여 그 이미지를 어디에 두어야 하는지를 계산한다. 백분율이 50%이면
이미지는 시작과 끝 사이의 중간으로 간다.
나는 작업하면서, 그리고 이 글을 쓰면서 많은 JavaScript 애니메이션 코드를 보았다. JavaScript 애니메이션은 매끄럽지 못하다는 평가를 받는다. 모든 JavaScript 애니메이션은 window.setInterval
메소드를 사용하여 수행된다. 이것은 콜백의 간격과 콜백 함수를 지정하는 타이머 메소드이다. 웹 상의 대부분의 코드가 작성되어
함수가 호출될 때 마다 애니메이션은 한 단계씩 움직인다. 브라우저에 명령한 간격은 단순한 제안이기 때문에 작동하지 않는다. 20
밀리초를 지정하면 25 밀리초로 호출된다. 브라우저는 싱글-쓰레드이기 때문에 타이머에 의존할 수 없다.
솔루션은 Date
객체에 valueOf
메소드를 사용하여 애니메이션 시작 후에 얼마나 오래 걸리는지 보는 것이다. 그 차이는 밀리초 정도이고 setInterval
타이머가 실행되었을 때 어느 정도의 애니메이션이 수행되었는지를 확인하는데 사용된다. 이 메소드는 매끄러운 애니메이션 작동을 보장한다.
세 개의 핵심 클래스 중 마지막은 Ken Burns Effects이다. 이 효과는 Animation
객체를 통해 이미지에 적용된다.(Listing 6)
Listing 6: KenBurnsAnimations.js
function KenBurnsFader( img, windowSize ) { this.img = img; this.windowSize = windowSize; } KenBurnsFader.prototype.apply = function( percent ) { var opacity = 100; if ( percent <= this.windowSize ) opacity = ( percent / this.windowSize ) * 100; else if ( percent >= ( 100 - this.windowSize ) ) opacity = ( ( 100 - percent ) / this.windowSize ) * 100; this.img.set_opacity( opacity ); } function KenBurnsZoomer( img, start, end, cw, ch ) { this.start = start; this.end = end; this.img = img; var wr = this.img.width / cw; var nw = this.img.width * wr; var nh = this.img.height * wr; this.sw = ( nw * ( this.start / 100 ) ); this.ew = ( nw * ( this.end / 100 ) ); this.sh = ( nh * ( this.start / 100 ) ); this.eh = ( nh * ( this.end / 100 ) ); this.dw = ( this.ew - this.sw ) / 100; this.dh = ( this.eh - this.sh ) / 100; } KenBurnsZoomer.prototype.apply = function( percent ) { this.img.set_size( this.sw + ( this.dw * percent ), this.sh + ( this.dh * percent ) ); } function KenBurnsMover( img, sx, sy, ex, ey, cw, ch ) { this.img = img; this.sx = sx / 100; this.ex = ex / 100; this.sy = sy / 100; this.ey = ey / 100; this.cw = cw; this.ch = ch; this.wr = this.img.width / this.cw; } KenBurnsMover.prototype.apply = function( percent ) { var nw = this.img.current_width * this.wr; var nh = this.img.current_height * this.wr; var cntw = ( ( this.cw / 2 ) - ( nw / 2 ) ); var cnth = ( ( this.ch / 2 ) - ( nh / 2 ) ); var sx = ( nw * this.sx ); var ex = ( nw * this.ex ); var sy = ( nh * this.sy ); var ey = ( nh * this.ey ); var dx = ( ex - sx ) / 100; var dy = ( ey - sy ) / 100; var x = cntw + sx + ( dx * percent ); var y = cntw + sy + ( dy * percent ); this.img.set_position( x, y ); } |
이러한 세 개의 클래스들은 이미지에 적용된 다른 효과들을 다룬다. KenBurnsFader
클래스는 투명도를 사용하여 이미지의 페이드인 및 페이드아웃을 다룬다. KenBurnsZoomer
클래스는 줌 시작 레벨에서 끝 레벨까지 이미지의 움직임을 핸들 한다. KenBurnsMover
클래스는 시작 포인트에서 끝 포인트로 이미지 움직임을 다룬다.
약간 실험한 결과 가장 뚜렷한 움직임 효과는 창의 중앙에 비례하여 이미지가 코너에서 코너로 움직일 때였다. KenBurnsMover
클래스의 apply
메소드에는 복잡한 수학이 포함되어 있어 저장 <div>
중앙과 비례하여 이미지를 움직일 뿐만 아니라 <div>
태그의 크기에 비교하여 이미지를 사이징하여 애니메이션이 작은 창에서는 작게, 큰 창에서는 크게 나타난다. 확대는 창의 높이에 기반한다.
이러한 기본 클래스를 사용하여 비 Ajax DHTML 버전의 슬라이드 쇼를 구현해 보기로 한다.(Listing 7)
Listing 7. 비 Ajax 슬라이드 쇼
<html> <head> <style type="text/css"> body { background: black; margin: 0px; padding: 0px; } </style> <script src="KenBurnsAnimations.js"> </script> <script src="Animation.js"> </script> <script src="ImageInfo.js"> </script> <script> var g_animationManager = new AnimationManager( 50 ); var g_current_slide = 0; var g_slides = []; var g_directions = [ { sx: [ -30, 0 ], ex: [ 5, 40 ], sy: [ -30, 0 ], ey: [ 5, 40 ] }, // nw -> se { sx: [ 5, 40 ], ex: [ -30, 0 ], sy: [ 5, 40 ], ey: [ -30, 0 ] }, // ne -> sw { sx: [ 5, 40 ], ex: [ -30, 0 ], sy: [ 5, 40 ], ey: [ -30, 0 ] }, // se -> nw { sx: [ -30, 0 ], ex: [ 5, 40 ], sy: [ 5, 40 ], ey: [ -30, 0 ] } // sw -> ne ]; g_animationManager.on_finished = function() { g_current_slide++; if ( g_current_slide >= g_slides.length ) g_current_slide = 0; g_slides[ g_current_slide ].start(); } function rnd( start, end ) { return ( Math.random() * ( end - start ) ) + start; } function load_slides( images ) { var ic = document.getElementById( 'imgContainer' ); for( var i in images ) { var img = images[i]; var imgObj = document.createElement( 'img' ); imgObj.style.position = 'absolute'; imgObj.style.left = '0px'; imgObj.style.top = '0px'; imgObj.style.visibility = 'hidden'; ic.appendChild( imgObj ); var ii = new ImageInfo( img.src, img.width, img.height, imgObj ); var szoom = rnd( 50, 100 ); var ezoom = rnd( 70, 120 ); var d = parseInt( ( Math.random() * g_directions.length ).toString() ); var di = g_directions[ d ]; var sx = rnd( di.sx[0], di.sx[1] ); var sy = rnd( di.sy[0], di.sy[1] ); var ex = rnd( di.ex[0], di.ex[1] ); var ey = rnd( di.ey[0], di.ey[1] ); g_slides.push( new Animation( g_animationManager, ii, 10, [ new KenBurnsZoomer( ii, szoom, ezoom, ic.clientWidth, ic.clientHeight ), new KenBurnsMover( ii, sx, sy, ex, ey, ic.clientWidth, ic.clientHeight ), new KenBurnsFader( ii, 30 ) ] ) ); } } function start_slides() { g_slides[ g_current_slide ].start(); } </script> </head> <body> <div style="position:relative;width:100%;height:100%;overflow:hidden;" id="imgContainer"> </div> <script> var images = [ { src: 'images/megan1_875_700.jpg', width: 875, height: 700 }, { src: 'images/oso1_875_700.jpg', width: 875, height: 700 }, { src: 'images/oso2_873_700.jpg', width: 873, height: 700 } ]; load_slides( images ); start_slides(); </script> </body> </html> |
브라우저에서 보이는 모습은 거칠다. 따라서 이 쇼를 찍어 그림 6에 나타냈다.
그림 6. 슬라이드 쇼 스냅샷
이 페이지는 <script>
태그의 src
아이템을 통해 기본 클래스에 가져오는 것으로 시작한다. 그러한 클래스가 설치된 후에 새로운 함수들이 추가되어 전체 메커니즘을 가져온다. (load_slides
와 start_slides
) load_slides
함수는 src
, width
, height
스팩의 이미지 어레이를 취해서 <image>
태그와 애니메이션을 만든다.
애니메이션 매니저에 붙은 또 다른 함수인 on_finished
는 애니메이션이 완료될 때 마다 호출된다. 그 공지를 사용하여 다음 슬라이드로 이동하거나,모든 슬라이드의 애니메이션이 완료될 경우 리스트에 있는 첫 번째 슬라이드로 돌아갔다.
load_slides
를 보면, 이것은 g_directions
라고 하는 어레이를
참조한다. 이 어레이에는 이미지가 움직임을 시작하고 끝내는 장소를 지정하기 위해 슬라이더 로더가 사용하는 범위들이 포함된다.
가장 뚜렷한 효과는 코너에서 코너로 간다. 주석을 보면 이러한 범위는 슬라이드의 움직임을 북동, 남동, 북서, 남서 방향의
조합으로 지정한다. 마지막 <script>
태그는 이미지의 어레이를 정의하고 load_slides
와 start_slides
함수를 사용하여 슬라이드 쇼를 시작한다.
마지막으로 Ajax 버전의 슬라이드 쇼를 만든다. 하드 코딩 이미지 리스트를 slides.php 서비스에서 검색된 것으로 대체한다.
Ajax 버전의 슬라이드 쇼 코드는 Listing 8을 참조하라.
Listing 8. Ajax 슬라이드 쇼 코드
<html> <head> <style type="text/css"> body { background: black; margin: 0px; padding: 0px; } </style> <script src="KenBurnsAnimations.js"> </script> <script src="Animation.js"> </script> <script src="ImageInfo.js"> </script> <script src="SlideShow.js"> </script> </head> <body> <div style="position:relative;width:100%;height:100%;overflow:hidden;" id="imgContainer"> </div> <script> function processReqChange() { if (req.readyState == 4 && req.status == 200 && req.responseXML != null) { var items = []; var nl = req.responseXML.getElementsByTagName( 'slide' ); for( var i = 0; i < nl.length; i++ ) { var nli = nl.item( i ); var src = nli.getAttribute( 'src' ).toString(); var width = parseInt( nli.getAttribute( 'width' ).toString() ); var height = parseInt( nli.getAttribute( 'height' ).toString() ); items.push( { src: src, width: width, height: height } ); } load_slides( items ); start_slides(); } } function loadXMLDoc( url ) { req = false; if(window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch(e) { req = false; } } else if(window.ActiveXObject) { try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) { try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) { req = false; } } } if(req) { req.onreadystatechange = processReqChange; req.open("GET", url, true); req.send(""); } } loadXMLDoc( "http://localhost/kenburns/slides.php" ); </script> </body> </html> |
start_slides
와 load_slides
함수를 SlidesShow.js 라고 하는 외부 JavaScript 파일로 이동하여 파일 길이를 짧게 유지했다. 나머지 코드는 Listing 2의 Ajax 테스트 페이지와 비슷하다. 경고 창을 삽입하거나 아이템을 테이블에 추가하는 대신 이 코드는 슬라이드 정보의 어레이를 만들고 load_slides
와 start_slides
를 호출한다.
Ken Burns Effect를 사용하여 이미지를 움직이는 Ajax 슬라이드 쇼가 생겼다.
이 글에서는 객체 지향 JavaScript 코드를 사용했다. JavaScript는 완전한 객체 지향 언어이고 class
와 interface
키워드를 사용하지 않더라도 코드가 깨끗하게 유지된다. 가능하다면 Ajax 프레임웍을 사용할 것을 권한다. 여기에서는 경량의
Ajax 솔루션을 보여주고 싶었기 때문에 사용하지 않았다. 하지만 요즘 프레임웍에서는 Ajax와 DHTML 코드를 더 잘 작성할 수
있다.
이 글에서 설명한 것 외에도 Ajax 슬라이드 쇼에 대해 추가로 권할 사항들이 있다.
- 시간 기반 애니메이션을 사용하라.
setInterval
코드를 사용하게 되는 단계 기반 애니메이션은 움직임이 매끄럽지 못하다. - 시각적 엘리먼트를 위해 DHTML로 코드를 프로토타이핑 하고 나중에 Ajax를 추가하라. 오프라인에서 DHTML 코드로 작업을 할 수 있다는 의미이다.
- 데이터를 렌더링 하는 DHTML 사용자 인터페이스 컴포넌트에서 서버로 연결하는 Ajax 코드를 구분하라. 이렇게 해서, Ajax를 사용하여 데이터를 얻을 수 없을 때 라도 그 컴포넌트를 사용할 수 있다.
innerHTML
함수 대신createElement
와appendChild
함수를 사용하여 페이지의 콘텐트를 조절하라.- 당신이 지원하고 싶은 모든 브라우저에 대해 클라이언트 측 코드를 반드시 검사하라. 또한 호환성 문제들을 규명하고 해결하라. 클라이언트 측 픽스를 재사용 가능한 JavaScript 도움말 함수와 클래스로 캡슐화 하라.
- 다중 애니메이션이 포함된 복잡한 인터페이스의 경우 먼저 스토리보드를 사용하여 코딩 하기 전에 고객이 원하는 것이 정확히 무엇인지를 파악하라. 스토리보드는 애니메이션 버전의 코드라고 할 수 있다. JavaScript 애니메이션은 작성이 빠르기 때문에 코딩 전에 원하는 것이 무엇인지를 규명하는 것이 좋다. 그렇지 않으면 많은 것을 잃게 된다.
- 커리어의 관점에서 볼 때, 데이터베이스와 비즈니스 로직에 집중했던 Web V1.0 시절의 "백엔드 엔지니어"는 Web V2.0 세계에서는 환영 받지 못한다. 모든 요청들이 HTML을 기대하는 것은 아니라는 것을 깨달아야 할 때이다. 또한 Ajax와 DHTML은 자신의 기술에 실제 돈을 지급했던 진정한 엔지니어를 위한 툴이다.
이 글에서 보여주었던 것과 같은 동적인 슬라이드 쇼를 만들기 위해 플래시나 비슷한 애플리케이션이 필요했었다. 풍부한 효과를 가진 DHTML과 Ajax 지원이 포함된 현대적인 브라우저를 사용하면 브라우저에 놀라운 일을 할 수 있다. 고객은 페이지를 검색하여 놀라운 그래픽 효과를 누릴 수 있게 되었다.
설명 | 이름 | 크기 | 다운로드 방식 |
---|---|---|---|
Code and file samples for this article | x-ajaxslideshow/kenburns.zip | 705KB | HTTP |
교육
-
developerWorks XML zone
-
Technical events and Webcasts
-
PHP home page
-
MSDN.
-
Dynamic HTML
-
W3C site
-
Flickr API documentation site
-
The Ken Burns Effect, IMDb.
제품 및 기술 얻기
토론
Jack D. Herrington은 20년 경력의 소프트웨어 엔지니어이다. Code Generation in Action, Podcasting Hacks, PHP Hacks(출간 예정)의 저자이기도 하다. 30개 이상의 기술자료도 집필했다. (jack_d_herrington@codegeneration.net)