DHTML と XML を使った表現力豊かな Ajax スライドショー

Ajax を使って動的な HTML のエフェクトを作成する

「Ken Burns エフェクト」を使ってアニメーション化された、Ajax (Asynchronous JavaScript and XML)クライアント・サイド・スライドショーの作成方法を学びましょう。ここでは、Ajax 用の XML データ・ソースを作成し、クライアントから XMLデータを要求し、そしてその XML を使って HTML 要素を動的に作成し、アニメーション化する方法を学びます。

Jack Herrington (jherr@pobox.com), Editor-in-Chief, Code Generation Network

Jack D. Herringtonは、20年以上の経験を持つシニア・ソフトウェア・エンジニアです。著者には、「Code Generation in Action」、「Podcasting Hacks」、そして近々刊行予定の「PHP Hacks」の3冊があります。彼は30本以上の技術記事も執筆しています。



2007年 3月 16日 (初版 2006年 4月 18日)

Web 2.0 革命での流行語を 1 つあげるなら、それは Ajax (Asynchronous JavaScript and XML) でしょう。GoogleMaps™ マップ・サービスや Gmail™ Web メールなどのアプリケーションが持つ、クライアント・サイドの対話機能によって、Ajaxは刺激的で、そして便利なものになります。Ajax に含まれる技術、つまり HTML (Hypertext Markup Language)、JavaScriptコーディング、CSS (Cascading Style Sheets)、XML、そして非同期 Web リクエストによって、これまで私達が WebV1.0 で見てきたものよりもはるかに強力な Web 対話機能を実現することができます。もちろん、こうした技術は Microsoft®Internet Explorer® V4 の頃からあったものですが、その利点が Internet Explorer 以外の有名なアプリケーションによって明らかになったのは、ごく最近のことです。

Ajax の実装は、どれくらい難しいのでしょうか。Ajax モデルの各要素は比較的容易に学ぶことができます。しかし難しいのは、そうした要素同士を組み合わせ、シームレスなエクスペリエンスを実現することです。多くの場合、クライアント・サイドとサーバー・サイドを別の人がコーディングするため、問題はさらに複雑になります。この記事では、たった1 人で Ajax ベースの小さなスライド表示アプリケーションを 2、3 時間で作成する方法を示します。

Ajax を使ったスライドショー

Macintosh® の Apple® iPhoto® のような個人用画像管理アプリケーションによって、スライドショー表示の人気が高まりました。スライドショーでは、画像はフェードイン、フェードアウトされながら、一定の時間間隔で次々に表示されます。さらに、「KenBurns エフェクト」として知られるようになった視覚効果を使って、画像を動かしたりズームしたりすることもできます。

この例では、まずブラウザーがサーバーから画像のリストをダウンロードします。次に、そうした画像リストと DHTML (Dynamic HTML)を使って、スライドショーを作成します。そして、その画像をランダムでゆっくりした動きやズーム、フェードによってアニメーション化し、Macromedia®Flash などの重量級のアニメーション・ツールをダウンロードしなくても Ken Burns エフェクトを満足するものを作成します。


アーキテクチャー

Ajax は何が異なるかを理解するためには、まず現在の Web プログラミング・モデルを理解する必要があります。図 1 は、クライアントとサーバーとの間の単純な対話動作を示したものです。

図 1. Web V1.0 でのクライアントとサーバー間の対話動作モデル
図 1. Web V1.0 でのクライアントとサーバー間の対話動作モデル

Web ブラウザー、つまりクライアントは、Web サーバーに対して GET リクエストあるいは POST リクエストを行います。サーバーは HTML レスポンスをフォーマットします。クライアントはその HTML を構文解析し、ユーザーに表示します。ユーザーが別のリンクまたはボタンをクリックすると、サーバーに対して別のリクエストが行われ、サーバーから返される新しいページで現在のページが置き換えられます。

この新しいモデルは、先のモデルよりも非同期です (図 2)。

図 2. Ajax でのクライアントとサーバー間の対話動作モデル
図 2. Ajax でのクライアントとサーバー間の対話動作モデル

この新しいモデルでは、サーバーは (これまでと同じように) HTML ページを返します。しかし今度は、このページの中に少しばかり JavaScriptコードが入っています。このコードは、必要に応じて、さらに情報を要求するためにサーバーをコールバックします。こうしたリクエストは、REST (RepresentationalState Transfer) サービスに対する単純な GET リクエストとして、あるいは SOAP で要求される POST リクエストとして行われます。

次に JavaScript コードは新しいデータを反映するために、レスポンス (多くの場合は XML としてエンコードされています) を構文解析し、そのページのHTML を動的に更新します。XML の他に、JSON (JavaScript Serialized Object Notation) フォーマットでエンコードされたデータを返すこともできます。JSONデータの方がブラウザーにとっては理解が容易ですが、他のタイプのクライアントにとってはそうではありません。XML を返す利点は、ブラウザー以外のクライアントがデータを解釈できる点です。どちらを返すかは、設計者とアプリケーション次第です。


画像情報を用意する

Ajax スライドショーを作成するための第一歩は、REST データ・サービスを用意することです。この例では、利用可能なすべてのスライドショー画像とそのサイズ(幅と高さ) を返す PHP ページを使います。すべての画像は images という名前のディレクトリーに置かれています。ファイル名は name_width_height.jpg、例えばoso1_768_700.jpg のようになっています。このファイル名の意味は、このファイルが Oso (私が飼っている犬のうちの 1 匹)の写真であり、幅が 768 ピクセルで高さが 700 ピクセルであるということです。こうすれば Adobe® PhotoShop®や Macromedia Fireworks をわざわざ開かなくても容易に画像の幅と高さがわかるため、私はいつもこの命名規則を使っています。

この画像のリストを作成するために、リスト 1 のPHP サーバー・コードを使います。

リスト 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(document object model) を作成させることは重要です。このコードは <slides> タグを開始し、次に画像ディレクトリーを読み取り、そこに見つかる画像それぞれに対して<slides> タグを作成します。そして最後に <slides> タグを閉じます。

Mozilla® Firefox® ブラウザーをこのページ (この場合は私の localhost の kenburns というディレクトリーにホストされています) までナビゲートすると、図 3 のような結果が表示されます。

図 3. slides.php サーバー・スクリプトの出力
図 3. slides.php サーバー・スクリプトの出力

ここには、私の娘の画像 1 枚と私の犬の画像 2 枚、という 3 枚の画像があります。もちろん、もっと詳細なものやマルチメディアも自由に追加することはできますが、この例では単純にしておきます。


XML を取得する

次のステップは、サービスからデータを読み取り、またブラウザーとサーバーとの間の Ajax 接続を検証する HTML ページを作成することです(リスト 2)。この HTML コードは JavaScript コードを埋め込んでおり、XML を取得し、そしてサーバーが返すテキストに含まれるアラートを表示します。

リスト 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
図 4. アラート・ウィンドウに表示された XML

幸先の良いスタートです。確かにサーバーから XML データを受信しています。しかしここで、いくつかの注意点を指摘しておきましょう。まず、URLが、絶対パス、ドメイン名などであることに注目してください。Ajax では、このスタイルの URL のみが有効です。Ajax JavaScriptコードを作成するサーバー・コードは、必ず、有効で完全形式の URL を作成します。

もう 1 つ、ここでは明白ではありませんが、Ajax でのセキュリティーに関する注意事項があります。JavaScript コードは、単純に任意のURL を要求することはできません。URL は、そのページと同じドメイン名を持っている必要があります。この場合では、ドメイン名は localhostです。しかし重要な点として、www.mycompany.com の HTML を描画した後でスクリプトが data.mycompany.comからデータを取得することはできないことに注意してください。両方のドメインは、サブドメインを含めて、完全に一致する必要があります。

もう 1 つ、興味深いコードは、loadXMLDoc で、苦労してリクエスト・オブジェクトを作成しているように見えますが、なぜそんな面倒なことをするのでしょう。それは、バージョン 7 以前の InternetExplorer には、XMLHTTPRequest オブジェクト・タイプが組み込まれておらず、そのため Microsoft ActiveX® コントロールを使う必要があるからです。

最後に、processReqChange 関数を見ると、readyState4 と等しいかどうか、そして status200 に設定されているかどうかを調べていることがわかります。readyState の値が 4ということは、トランザクションが完了していることを意味します。status の値が 200 ということは、このページが有効であることを意味します。また、もしページが見つからないと、ブラウザーに表示されるものと同じエラー・メッセージ404 が表示されます。これは単にサンプル・コードなので例外ケースを処理していませんが、皆さんが正式に作成する Ajax コードは、エラーを返すリクエストを処理する必要があります。


動的に HTML を作成する

スライドショーの作成方法を説明する前に、現在の例を拡張し、サーバーから受信した XML リクエストの結果を含む HTML 表を processReqChange 関数が作成するようにします。これによって、XML を読めること、そして XML から動的に HTML を作成できること、という2 つをテストすることができます。

リスト 3 は、返された XML から表を作成する、更新されたコードを示しています。

リスト 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. 更新されたテスト・ページ
図 5. 更新されたテスト・ページ

更新された processReqChange コードは、今度は responseText テキストではなく responseXML オブジェクトを調べます。また、getElementsByTagName を使ってすべての <slide> タグにアクセスします。そして<slide> タグからsrcwidthheight 属性を解析し、また document オブジェクトの createElement メソッドを使って、データを保持するための行とセルを作成します。この、createElement メソッドを使う方法は、従来の方法よりもはるかに確実です。この方法では、表の内容を持つ HTML ストリングを作成し、そして innerHTML を使って既存の要素にデータを追加します。


スライドショーを作成する

スライドショーの中の画像を識別できる Web サービスが用意できたので、今度はこうしたスライドを表示し、Ken Burns エフェクトを適用したアニメーションを実行するクライアント・コードが必要です。そのためには、下記の3 つの基本機能を実行する一連の JavaScript オブジェクトを用意する必要があります。

  1. 画像をカプセル化する
  2. 基本的なアニメーション・エンジンを提供する
  3. エフェクト (移動やズーム、フェードなど) を実行する

画像をカプセル化する

まず画像コンテナーから始めることにし、ImageInfo というクラスを作成します (リスト 4)。

リスト 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';
}

スライドショーの中の画像それぞれに対して、対応する 1 つの ImageInfo オブジェクトがあります。このオブジェクトは、その画像に関する既知の情報 (srcwidthheight) をカプセル化します。またこのオブジェクトは、文書の中の画像を表示するHTML <img> タグへの参照と、画像の移動や透明度の設定などを行える便利なヘルパー・メソッドを持っています。Firefox などの Gecko®ベースのブラウザーでは、MozOpacity スタイルを使って透明度が設定されることに注意してください。Internet Explorer ではフィルター効果が使われます。

単純なアニメーション・エンジンを作成する

次に、単純なアニメーション・エンジンを作成します。このコードが、リスト 5 に示す Animation.js ファイルです。

リスト 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()
{
}

リスト 5 には、AnimationAnimationManager という、2 つのクラスが示してあります。AnimationManager クラスはタイマーをコントロールし、自分が持っている Animation オブジェクトのリストの最初の項目にアニメーション・メッセージを送信します。Animation オブジェクトがアニメーションを終了したことを伝えると、AnimationManager クラスは次の項目に移り、同じことが繰り返されます。

Animation オブジェクトは、秒で指定される一定期間、特定の画像に一連のエフェクトを適用します。何パーセント完了したかというメッセージを計算し、それを各エフェクトのapply メソッドに送信するのは Animation オブジェクトの仕事です。そうするとエフェクトは、そのパーセンテージを基に、画像に対して何をすべきかを計算します。例えば、「移動」というエフェクトは開始点と終了点を知っており、パーセンテージを基に、画像をどこに置くべきかを計算します。もしパーセンテージが50% であれば、その画像は開始点と終了点の中間にある必要があります。

私は仕事の一部として、またこの記事のための調査の中で、数多くの JavaScript アニメーション・コードを見てきました。JavaScriptアニメーションは、ぎこちないと批判されることがよくあります。これは、どの JavaScript アニメーションも window.setIntervalメソッドを使って実行されるためです。window.setInterval メソッドはタイマー・メソッドであり、このメソッドの中で、コールバックの間隔とコールバックすべき関数の両方を指定します。Web 上のコードの大部分は、この関数がコールされるたびにアニメーションがワン・ステップ進むように作られています。しかしこれは、実際にはうまく動作しません。これは、ブラウザーに間隔を命令しても、それは単なる助言にすぎないためです。例えば20 ミリ秒と指定したとすると、ある時は 25 ミリ秒でコールされるかもしれませんが、その次は 1 秒後にコールされるのかもしれません。ブラウザーは単一スレッドなので、タイマーに頼ることはできないのです。

これを解決するためには、Date オブジェクトの valueOf メソッドを使い、アニメーションの開始からの経過時間を調べます。この経過時間はミリ秒単位であり、これを使って、setInterval タイマーがタイムアウトした時にアニメーションを何パーセント実行するかを判断します。この方法によって、指定した期間だけ実際に継続されるスムースなアニメーションを実現することができます。

エフェクトを実行する

3 つのコア・クラスの最後は、Ken Burns エフェクトです。Animation オブジェクトによって、画像に Ken Burns エフェクトが適用されます (リスト 6)。

リスト 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 );
}

これら 3 つのクラスは、画像に適用される 3 種類のエフェクトを処理します。KenBurnsFader クラスは、透明度 (opacity) を使って画像のフェードイン処理とフェードアウト処理を行います。KenBurnsZoomer クラスは、ある開始ズーム・レベルから終了ズーム・レベルまで、画像のズーム処理を行います。KenBurnsMover クラスは、(画像サイズのパーセントで指定される) 開始点から終了点までの移動処理を行います。

少し実験してみたところ、ウィンドウの中心に対して、コーナーからコーナーへ画像を移動する場合が、最も視覚的に訴える効果のある動きであることがわかりました。KenBurnsMover クラスの apply メソッドには複雑な計算が含まれており、画像を含む <div> タグの中心に対して相対的な位置に画像を移動できる他、<div> タグのサイズとの相対的な割合で画像サイズを変更することもできます。そのため、小さなウィンドウではアニメーションは小さく表示され、大きなウィンドウでは大きく表示されます。この拡大縮小はウィンドウの高さに基づいて行われます。

Ajax を使わない DHTML 実装

こうした基礎クラスが用意できたら、今度はテスト用に、Ajax を使わないバージョンのスライドショーの DHTML を実装する番です (リスト 7)。

リスト 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. スライドショーのスナップショット
図 6. スライドショーのスナップショット

このページは、<script> タグの src 項目を使ってベース・クラスをインストールすることから始まります。これらのクラスがインストールできると、新しい関数 (load_slidesstart_slides) が追加され、全体としての機構ができあがります。load_slides 関数は、画像の srcwidthheight 仕様の配列を使って、そして <image> タグとアニメーションを作成します。また start_slides 関数は最初の項目を使ってスライドショーを開始します。

アニメーション・マネージャーに付いている、もう 1 つの関数 (on_finished) は、アニメーションが終了するたびに呼ばれます。ここではこの通知を、次のスライドに移動するために、あるいはすべてのスライドのアニメーションが終了した時にリストの最初のスライドに戻るために使用しています。

load_slides に戻ると、load_slidesg_directions という配列を参照していることに注意してください。この配列には一連のランダムな範囲が含まれており、スライド・ローダーは画像の移動をどこで開始し、終了するかを、こうした範囲を使って指定します。最も視覚効果が高いのはコーナーからコーナーへの移動です。コメントを見るとわかるように、これらの範囲は、スライドの移動を北東、南東、北西、南西それぞれの組み合わせで指定しています。最後の<script> タグは画像の配列を定義しており、そして load_slides 関数と start_slides 関数を使ってスライドショーを開始しています。


Ajax スライドショーを作成する

このプロセスの最後のステップは、Ajax バージョンのスライドショーを作成することです。これはつまり、ハードコーディングされた画像リストを、slides.phpサービスから取得したものと置き換えるということです。

Ajax バージョンのスライドショーのコードをリスト 8 に示します。

リスト 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>

Ajax スライドショーのライブ・デモ

ライブ環境でのスライドショーのデモを見るには、この Open link to view slideshow in new windowAjax スライドショーのオンライン版を見てください。

私はファイルの長さを短くするために、start_slides 関数と load_slides 関数を、SlidesShow.js という外部の JavaScript ファイルに移動しました。それ以外のコードは、リスト 2 の Ajax テスト・ページで示したものと似ています。しかしこのコードは、アラート・ウィンドウを挿入したり表に項目を追加したりするのではなく、スライド情報の配列を作成し、そしてload_slidesstart_slides を呼び出します。

そして、これで終わりです。Ken Burns エフェクトを使って動的に画像を移動し、ズームし、フェードする Ajax スライドショーが完成しました。


Ajax スライドショーを必要に応じて変更する

この記事では、可能な限りオブジェクト指向の JavaScript コードを使いました。JavaScript は完全にオブジェクト指向の言語であり、classclass などのキーワードを使わないかもしれませんが、コードをクリーンで維持管理しやすいものに保つことができます。また、できれば Ajax フレームワークを使った方が得策です。ここでAjax フレームワークを使わなかった理由は、軽量の Ajax ソリューションを示したかったからです。しかし現在は非常に多くのフレームワークがあり、こうしたフレームワークを使うことで、移植性の高いAjax コードや DHTML コードを容易に作成することができます。

この記事で説明した注意点の他に、Ajax スライドショーを作成する際の推奨事項として、下記をあげておきます。

  • 時間をベースとするアニメーションを使う。setInterval コードを使ってステップ・ベースのアニメーションを作ろうとすると、ぎこちない動きになってしまいます。
  • 視覚要素に対するコードを DHTML でプロトタイプ化し、その後で Ajax 関係のものを追加する。そうすれば、オフラインで DHTML コードを扱うことができます。
  • データを描画する DHTML の UI (user interface) コンポーネントからサーバーに接続する、Ajax コードを区画化する。こうすれば、データを取得するためにAjax を使わない場合でも UI コンポーネントを使うことができます。
  • ページの内容の調整には、innerHTML 関数ではなく createElement 関数と appendChild 関数を使う。
  • サポート対象のブラウザーすべてに対して、必ずクライアント・サイド・コードをチェックする。また、突き当たった互換性問題のリストと、そうした問題をどう解決したかに関する記録を持つ。クライアント・サイドの修正を、再利用可能なJavaScript ヘルパー関数とクラスにカプセル化するように心がける。
  • 技術者の経歴という観点から見ると、データベースとビジネス・ロジックのみに焦点を絞る Web V1.0 の世界の「バックエンド・エンジニア」は、WebV2.0 の世界では限られています。現在では、サーバーへのリクエストが、すべて HTML を要求するとは限らないことを認識する必要があります。また、Ajaxと DHTML は、スキルに対して給料が払われる本物のエンジニアのための、本物のツールです。フロントエンドは、単にデザイナーのためだけのものではありません。
  • 技術者の経歴という観点から見ると、データベースとビジネス・ロジックのみに焦点を絞る Web V1.0 の世界の「バックエンド・エンジニア」は、Web V2.0 の世界では限られています。現在では、サーバーへのリクエストが、すべて HTML を要求するとは限らないことを認識する必要があります。また、Ajax と DHTML は、スキルに対して給料が払われる本物のエンジニアのための、本物のツールです。フロントエンドは、単にデザイナーのためだけのものではありません。

これまでは、この記事で説明したような動的なスライドショーを作成するために Flash などのアプリケーションが必要でした。最近のブラウザーは、透明度など(さらに Internet Explorer では回転や、ぼかしなども)、表現力豊かな視覚効果を備えた DHTML を十分にサポートしている上、Ajaxもサポートしているため、驚くほどのことをブラウザー自体で行うことができます。これはつまり、皆さんの顧客は、装飾的なエクステンションをダウンロードしたり、安全ではない可能性のあるアプリケーションを実行したりする必要がないということです。顧客は単純に皆さんのページを訪れるだけで衝撃的なグラフィック効果を体験でき、その体験から、何度もそのページを訪れるようになるでしょう。


ダウンロード

内容ファイル名サイズ
Code and file samples for this articlex-ajaxslideshow/kenburns.zip705KB

参考文献

学ぶために

製品や技術を入手するために

  • 皆さんの次期開発プロジェクトをIBM trial softwareで構築してください。developerWorks から直接ダウンロードすることができます。
  • 著者がこの記事を書くきっかけとなった、Apple iPhotoを見てください。ただし、他のアプリケーションでも Ken Burns エフェクトを利用することはできます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development
ArticleID=249231
ArticleTitle=DHTML と XML を使った表現力豊かな Ajax スライドショー
publish-date=03162007