SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

JavaScriptとjQueryでデスクトップアプリに負けない操作性のWebアプリケーションを作る

ドラッグアンドドロップ、マウス選択、パン、ズームなどができるWebアプリケーション

デスクトップアプリケーションの操作性の再現

 要素の選択と移動は、すべてのデスクトップマネージャの基本です。ここでは、この2つを組み合わせてWebページ内で同じエクスペリエンスを再現し、デスクトップ上でのアイコン操作と同様にユーザーがアイテムを選択して移動できるようにします。選択されたアイテムを追跡して、それらのアイテムが一緒に移動するようにdrag()ハンドラを修正する方法をリスト4に示します。

リスト4 選択されたアイテムを全体として移動するドラッグハンドラ
$('.box').draggable({
  // ... omissis
  drag: function(ev, ui) {
  
    // if we are dragging a selected item, move
    // all the others selected ones as well.
    if ($sel.isSelected(ui.helper[0].id)) {
      for (id in $sel.allSelected()) {
        if (id != ui.helper[0].id) {
        
          // update their initial position
          // (stored in the top0 and left0 attributes)
          // with the relative offset of the current drag
          // (stored in ui.position)
          $('#' + id).css(
            'top',
            $('#'+id).data("top0") + ui.position.top);
          $('#' + id).css(
            'left', 
            $('#'+id).data("left0") + ui.position.left);              
        }
      }
    }
  },
  // ... omissis
});

パンとエンドレスデスクトップ

 ここまでは、ありふれた機能を再現しただけです。ここでスパイスを少し加えて、新たな動作を追加しましょう。表示するアイテムが多いと、1ページに収まらないことがあります。その場合、単純にページサイズを大きくしてスクロールバーを表示することもできますが、空間的なナビゲーションが必要になると厄介なことになります。それよりも良い代替策として、一種のエンドレスデスクトップで自由にパンする方法をユーザーに提供することが考えられます。Googleマップを使ったことがあれば、この考え方は既におなじみのはずです。つまり、マウスを使って地図をドラッグすることで、ブラウザのウィンドウに表示される部分を変更できるということです。

 これと同じ効果を再現するには、Webページの構造を変更しなければなりません。これを達成するためには、次の3つのレイヤが必要になるからです。

  • ''ユニバースレイヤ''―すべてのアイテムとオブジェクトが置かれているレイヤです。
  • ''ビューポートレイヤ''―ユーザーが直接見るものであり、境界外の全コンテンツをクリッピングします。ユニバースレイヤの上に位置します。
  • ''スクロールレイヤ''―マウスドラッグイベントをトラップして、相対動作をユニバースレイヤに伝達するためだけの透明なレイヤです。このレイヤはボタンによってアクティブになり、ビューポートをアクセス可能な状態に維持します。

 図4にエンドレスデスクトップ設定の概念図を示します。

図4 エンドレスデスクトップ効果の再現に必要なレイヤ
図4 エンドレスデスクトップ効果の再現に必要なレイヤ

 このレイアウトを実装するには、3種類のDIVを使用し、それらのCSS属性を慎重に選びます。最も重要なのがoverflow:hiddenです。これによりビューポートDIVをクリップ領域に変換します。詳しくはリスト5を見てください。

リスト5 「エンドレスデスクトップ」のレイヤ実装のためのHTMLとCSS
<style type="text/css" media="screen">
  #scroll-overlay {
    background: transparent url(img/scroll_big.png) no-repeat center center;
    position: absolute;
    z-index: -1;
    cursor: move;
  }

  /* This is the viewport layer */
  #container {
    position: absolute; 
    top: 150;
    left: 0;
    overflow: hidden;
    width: 300px;
    height: 300px;
  }

  #universe {
    position: absolute; 
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  }
</style>

<!-- ... omissis -->
<div id="container">
  <div id="universe">
    <div id="box1" class="box">Blue</div>
    <!-- All your contents here -->
  </div>
  
  <!-- When the scroll-trigger is clicked, it toggles
       the scroll layer -->
  <div id="scroll-trigger" title="Click to pan around" >
  </div>    
</div>

<!-- The scroll layer, initially hidden, with a button to
     dismiss it, once the panning is done -->
<div id="scroll-overlay" style="display: none">
  <div><button id="scroll-done">Done</button></div>
</div>

 残っている仕事は、ドラッグ動作をスクロールレイヤからユニバースレイヤに伝達するコードを書くことです(リスト6を参照)。

リスト6 ドラッグ動作をスクロールレイヤからユニバースレイヤに伝達するコード
// scroll-overlay is the id of the scroll layer.
$('#scroll-overlay').draggable({
  helper: function() {
    return $("<div />");
  },
  start: function(ev, ui) {
    // detect the current universe offset
    var offset = $('#universe').offset();
    $('#universe')
      .data("top0", offset.top)
      .data("left0", offset.left);
  },
  drag: function(ev, ui) {
    // apply to the universe the same relative movement
    // that occurred to the scroll layer.
    var universe = $('#universe');
    var offset = universe.offset();

    // dragDelta contains the initial offset between
    // the universe and scroll layers, if any.
    var dragTop = ui.position.top +
                  universe.data("top0") - dragDelta.top;
    var dragLeft = ui.position.left +
                   universe.data("left0") - dragDelta.left;

    $('#universe')
      .css('top', dragTop).css('bottom', -dragTop)
      .css('left', dragLeft).css('right', -dragLeft);
  }
});

 図5に最終結果を示します。

図5 制限されたビューポートでのパン:ドラッグ動作をスクロールレイヤからユニバースレイヤに伝達した結果
図5 制限されたビューポートでのパン:ドラッグ動作をスクロールレイヤからユニバースレイヤに伝達した結果

ズーム

 実装すべき最後の機能はズームです。考え方は比較的単純です。マウスホイールの上下の動きを検知し、そうした情報を使ってユーザーインターフェース内でアイテムを拡大縮小します。マウスホイールプラグインを利用すると、jQueryでさらにmousewheel()というコールバックを使用できるようになります。このコールバックはマウスホイールの操作が行われるたびに呼び出されます。このコールバックのdeltaパラメータにはホイールの移動量が含まれます。

 このコールバックがトリガーされたら、サイズ関連の適切なCSS属性を調整してDOM要素を拡大または縮小するようにします。ここで調整する属性としては、フォントサイズ、パディング、マージン、幅、高さがあります。ラスタイメージの拡大縮小には、もっと複雑な動作が必要になることがあります。つまり、ピクセル化(モザイク)を防ぐために、同じイメージを異なる解像度で格納しなければならないことがあります。

 この例では、DOM要素で絶対位置指定を使用しているので、それらの要素の位置属性(CSSのtopおよびleft属性)を調整するときは特別な注意が必要になります。このような調整は、ズームがマウスの現在位置でセンタリングされるように、マウスの現在位置を基準にして行わなければなりません。イメージの拡大縮小がないズーム処理の完全なロジックについては、リスト7を参照してください。

リスト7 ズームを実装するコード
// Scale a numeric attribute
function rescale(selector, property, percent) {
  var oldValue = parseFloat($(selector).css(property));
  $(selector).css(property, oldValue*percent);
}

// Scale according to a relative position
function rescaleRelative(selector, property, relative, percent) {
  var oldValue = parseFloat($(selector).css(property)) -
                 relative;
  $(selector).css(property, oldValue*percent + 
                  relative);      
}

$(document).ready(function() {
  $('#container').mousewheel(function(event, delta) {
   
    var containerTopOffset = 
      parseFloat($('#container').css('top'));
    var containerLeftOffset = 
      parseFloat($('#container').css('left'));

    // a scale factor for the delta received from the
    // mousewheel event
    var scale = 0.1;
    var percent = 1 + delta*scale;        
    $('.box').each(function() {

    // relative scale takes into account:
      // - mouse position
      // - container offset
      // - scaled element centering (width and height)
      rescaleRelative(this, 
        'top', 
          event.clientY - 
          containerTopOffset - 
          parseFloat($(this).css('height')) / 2,
        percent);
      rescaleRelative(this, 
        'left', 
          event.clientX - 
          containerLeftOffset - 
          parseFloat($(this).css('width')) / 2, 
        percent);          
      
      rescale(this, 'width', percent);
      rescale(this, 'height', percent);
      rescale(this, 'font-size', percent);
    });

    // prevent the mousewheel to trigger any
    // scrollbars that may exist.
    event.preventDefault();
  });
});

次のページ
アプリケーションの例

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Riccardo Govoni(Riccardo Govoni)

2003年からJ2EE開発者としてイタリアの金融サービス会社に勤務。ソフトウェアアーキテクトとして従来の銀行システムのWebフロントエンドの設計に従事。物理学修士。SCJP(Sun Certified Java Programmer)資格を持つ。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/3559 2009/03/06 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー