マウスホイールに動きで横にスクロールの横長ページを作成する【jQuery連載16】

基本的にウェブページは縦長になるようにレイアウトされ、上下にスクロールしながら閲覧することが一般的で、横スクロール型のサイトはFLASHサイトなどで一部見られたが、特別な理由が無い限り導入されることはありませんでした。しかしサイトとして他にはあまりみられない個性の一つとなるため、うまく導入すれば印象的なサイトにすることができるかもしれません。

横スクロールが採用されない理由のひとつは、ユーザがスクロールバーを横にスライドさせる操作がしづらいことが理由のひとつだが、そういったユーザの不便さをJavascript、jQueryで操作系の機能を補助することで解消することができる。

今回はメニューによるダイレクトリンクの他、マウスホイールやスワイプ操作で横スクロールするウェブページを作成してみます。

今回作成したサンプル

【サンプル】横スクロールするページ(ベーシック)
http://www.html5-memo.com/sample/jq-books/14/sample_basic.html

横スクロールの仕組み

まずはどのように横スクロールの仕組みを実装して行けば良いか、図を見ながら順を追って考えてみよう。本来は縦にならぶ、ひとまとまりの内容(※以下、コンテンツカラム)1つにつき1ページ分として分割し、横並びに配置していく。これら全てのコンテンツカラムをまとめるため、コンテナとなる1つの要素に格納する(※以下、これをカラムコンテナ)。また、後述の現在地機能で必要となるため、それぞれのコンテンツカラムの配置位置座標を任意の変数に記憶しておく。

カラムコンテナに格納した各コンテンツカラムは、今回のデフォルト仕様では、横幅(width)と縦幅(height)のサイズはブラウザ表示領域(※以下、スクリーン)に合わすため、スクリーンサイズよりも縦に長くなるコンテンツは画面内に収まるようにfloatなどで回り込ませる方法や、またはスクロールバーを表示してインラインフレームのように見せる方法もある。

カラムコンテナの用意ができたら、次にユーザのスクロール操作に連動して、縦(上下)移動ではなく、横(左右)移動するように処理を変更する。これは、スクロールバーやマウスホイールによる上下スクロールの処理をキャンセルし、カラムコンテナを左右移動させることで、あたかもスクリーンが左右にスクロールしているかのように見せることができる。

最後に、ナビゲーションメニューをクリックすると、各コンテンツカラムにダイレクトに移動する仕組みと、付加機能として現在表示されているコンテンツカラムとそれに対応するメニューが反転して表示される「現在地」機能も実装する。ダイレクトにリンクする機能は通常のアンカーリンクとなんら変わりはなく、本来上下に配置されているコンテンツが横にならんで配置されているので、必然横スクロールしながらその要素まで遷移してくことになるが、現在地機能は最初の手順で記憶しておいた各コンテンツカラムの配置位置座標を元に処理をおこなう。

まとめると以下の手順になる。(1)HTML上でコンテンツカラムを用意してカラムコンテナに格納する。(2)それぞれのコンテンツカラムを横に並べ、それぞれの縦横サイズをスクリーンサイズに合わせ、また配置位置座標を任意の記憶領域に保存しておく。(3)マウスホイール、スワイプなど、ユーザによるスクロール操作が行われた場合は、デフォルトの処理をキャンセルし、左右スクロール用の処理を実行する。(4)ナビゲーションメニューによるダイレクトリンク機能と現在地機能を実装する。以上、これら4つの実装を順を追っていきます。

HTML上でコンテンツカラムを用意してカラムコンテナに格納する

挙動が分かりやすいようにシンプルな内容の1つ目のサンプル(sample_basic.html)をベースに解説していく。まずは各コンテンツカラム(サンプルでは、div class=”column”)を用意し、これらを内包するコンテナ要素(サンプルでは、div class=”columnContainer”)に格納する。コンテンツカラムに付与したCSSセレクタ「column」が横スクロールで表示させる対象となり、その中に作成したCSSセレクタ「column_mainContent」は自動的に上下左右中央に配置される要素になる。実際はこの要素内にコンテンツを記述してくことになる。

JavascriptはjQuery本体の他、イージングを処理するjQuery.easingプラグイン、マウスホイールの操作を制御するjQuery.mousewheelプラグイン、スワイプなどのジェスチャー系操作を制御するjQuery.touchswipeプラグインを読み込んでおく。

<div id="contentContainer">

<div id="columnContainer">

<div id="column1" class="column">
<div class="column_mainContent">
<p class="number">1</p>
</div>
</div>

…省略…

<div id="column6" class="column">
<div class="column_mainContent">
<p class="number">6</p>
</div>
</div>

</div>
<!--/ columnContainer -->

<div id="subContainer">
<nav id="mainNavigation">
<ul>
<li><a href="#column1">CONTENT 1</a></li>
<li><a href="#column2">CONTENT 2</a></li>
<li class="notNarrow"><a href="#column3">CONTENT 3</a></li>
<li><a href="#column4">CONTENT 4</a></li>
<li class="notNarrow"><a href="#column5">CONTENT 5</a></li>
<li><a href="#column6">CONTENT 6</a></li>
</ul>
</nav>
<!--/ mainNavigation -->
</div>
<!--/ subContainer -->

</div>
<!--/ contentContainer -->

…省略…

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="js/jquery.mousewheel.js"></script>
<script src="js/jquery.easing.1.3.js"></script>
<script src="js/jquery.touchSwipe.min.js"></script>
<script src="js/script.js"></script>

コンテンツカラムを横に並べ、縦横サイズ調整と座標を保存する

コンテンツカラムを横に並べるにはそれぞれの要素が持つleftプロパティを調整していくことになるが、コンテンツカラム1つ1つのサイズはそのときのスクリーンサイズのよって異なるため、まずコンテンツカラムのサイズをスクリーンサイズに合わせ、その後それぞれのleftプロパティに座標を割り当て、その値を記憶しておく。記憶した値は後述のダイレクトリンク機能や現在地識別機能に利用します。

注意したいのが、ユーザーのウィンドウリサイズやスマートフォンやタブレット端末の向きの変更によってスクリーンサイズが変わり、それに応じてコンテンツカラムのサイズや座標も変更されるので、ユーザによるウィンドウリサイズや端末向きの変更(orientation change)の際は、再度変更された配置位置座標を記憶をしておく必要があります。

_$w.on('load', function(){

…省略…

        fitWindowScale(_$t); // 各カラムのサイズをスクリーンサイズに合わせる処理を実行
        setTargetPostion(_$t); // 各カラムの現在の位置を取得する処理を実行
      });

      _$w.on('resize orientationchange', function(){
        setTimeout( function() {
          fitWindowScale(_$t);
          setTargetPostion(_$t);
          adjustScrollPosition(-10);
        }, 200);
      });

…省略…

// 要素をウィンドウワイズにあわせる
    function fitWindowScale(_$t){
      var _win = getWindowInfo();
      _$cn = $(prop.columnContainer),
      _pos = { x: 0, y: 0};

      _$t.each(function(){
        // 各カラムの縦幅、横幅をスクリーン(ブラウザ表示領域)サイズにあわせ、カラムコンテナ内で横に順番に並ぶようにCSSを調整
        $(this).css({
          left: _pos.x,
          width: prop.fitWindowWidthScale ? _win.w : $(this).width(),
          height: _win.h
        });

        _pos.x += $(this).width(); // 次の横並び位置をカラム幅分右にずらす
        _pos.y += $(this).height(); // 仮想コンテナの総縦幅を決めるため、各カラム縦幅を加算していく

      });

      _$cn.width(_pos.x); // カラムコンテナの横幅を設定
      getVurtualContainerInfo(_pos.y); // 仮想コンテナの縦幅をbody要素の縦幅に設定

    }

    function setTargetPostion(_$t){
      prop.dataPossessor = [];
      _$t.each(function(){
        prop.dataPossessor.push(parseInt($(this).css('left'),10));
      });
      changeCurrent();

ユーザによるスクロールをキャンセルし、左右スクロール用の処理を実行する

ユーザのスクロールバーやマウスホイールによるスクロール操作をキャンセルし、左右スクロール用の処理を実行するという、仕組みのもっとも肝となる部分だ。今回のサンプルは、デフォルトではスクロールバーによる操作を無効にしているので、マウスホイールとスワイプによる操作について解説していきます。

マウスホイール操作やスワイプ操作の制御はJavascriptで行うことができるが、ブラウザやOSによってよってその実装方法が異なるため煩雑になりがちだ。今回導入するmousewheelプラグインやtouchSwipeプラグインは、それらの煩雑な仕様を隠蔽してシンプルに利用できるように提供してくれる。これらのプラグインを利用して、スクロールの向きや分量を取得したら、それらの値を左右スクロール用処理関数adjustScrollPosition()に送って処理を実行します。

     _$w.on('resize scroll', function(e){

        preventDefault(e);
        setTargetPostion(_$t);
        if($('body').is(':animated')) {
          return;
        }
        adjustScrollPosition();
      });

      // スワイプイベント
      _$w.on('touchmove',function(e){
          preventDefault(e);
      });
      
      _$w.swipe({
        swipeLeft:function(ev, dir, dist, dur, fin) {
          adjustScrollPosition(dist, dist);
        },
        swipeRight:function(ev, dir, dist, dur, fin) {
          adjustScrollPosition(-dist, dist);
        }
      });

      // マウスホイールイベント
      _$w.on('mousewheel', function(e) {
          preventDefault(e);
          var _dur = e.deltaY;
          _dur = _dur > 50 ? 50 : _dur < - 50 ? -50 : _dur;
          switch(prop.mode.wheel){
            case 0:
              _dur = 0;
              break;
            case 1:
              _dur *= -150;
              break;
            case 2:
              _dur *= 2;
              break;
            default:
          }
          adjustScrollPosition(_dur, Math.abs(_dur) * 2);
      });

…省略…

    function adjustScrollPosition(_diff, _dur, _ease){

        var _cn = getColumnContainerInfo(), // カラムコンテナの情報取得
        _win = getWindowInfo(), // ウィンドウ情報取得
        _vc = getVurtualContainerInfo(), // 仮想コンテナコンテナの情報取得
        _destX = _cn.left, // カラムコンテナの横位置の算出結果を格納
        _destY = 0; // カラムコンテナの縦位置の算出結果を格納

        if(prop.mode.scroll == 0) { // スクロールモードが0の場合は、縦位置を横位置変換する

          if(_diff == undefined) {
            _destY = _vc.top;
          } else {
            _diff *= _vc.ratio; 
            _destY = _vc.top  + _diff;
          }
          _destY = _destY <= 0 ? 0 : _destY >= _vc.maxpos ? _vc.maxpos : _destY;
          _destX = -_destY / _vc.ratio;

        } else {
          _destX -= _diff;
        }

        _destX = _destX >= 0 ? 0 : _destX <= -_cn.maxpos ? -_cn.maxpos : _destX;

        var _option = {  // アニメーションオプション
          duration: _dur ? _dur : prop.mode.scroll != 0 ? 500 : 0,
          easing: _ease || prop.transitionEasingDefault,
          queue: false,
          complete: function(){
            setTargetPostion($(prop.columnContainerItem));
          }
        };

        _vc.obj.stop(true, true).animate({
          scrollLeft: _destX,
          scrollTop: prop.mode.scroll == 0 ? _destY : 0,
        }, _option);
        _cn.obj.stop(true, true).animate({
          left: _destX,
          top: prop.mode.scroll == 0 ? _destY : 0
        }, _option);
    }

&#91;/javascript&#93;

<h2>ナビゲーションメニューによるダイレクトリンク機能と現在地機能を実装する</h2>

ナビゲーションメニューによってダイレクトに各コンテンツカラムへリンクする機能と、現在表示しているカラムコンテナの位置に対応してナビゲーションメニューが反転表示される現在地機能を実装します

ダイレクトに各カラムコンテンツへ遷移する仕組みは、通常のアンカーリンクによる仕組みとなんら変わりはなく、クリックされたナビゲーションメニューのhref属性を取得して、それと同じid属性(idセレクタ)を持つカラムコンテナの座標(leftプロパティ)へ向けてアニメーション遷移させれば良いでしょう。

現在地機能は、コンテンツカラムのリサイズと横並べ処理の際に記憶しておいた各コンテンツカラムの座標をもとに、現在表示されている座標と比較して、現在地の座標値の方が大きくなった時点を現在地として、その座標に対応するメニューを反転表示させています。

[javascript font_size="90%"]
      // ナビゲーションのクリックイベント
      $(prop.mainNavigation).find('a').on('click', function(e){
        preventDefault(e);
        // リンクの飛び先のキャッシュ
        var _href = $(this).attr('href'),
        _diff = 0;
        // リンク先へアニメーション遷移させる
        if(_href && $(_href).length > 0) {
          _diff = getColumnContainerInfo().left + parseInt($(_href).css('left'), 10);
          // スクロール位置を各カラムの仮想コンテナ内の縦位置に移動させる。縦のスクロール移動は別途設定したスクロールイベントによって、横のスクロールに変換される
          adjustScrollPosition(_diff, prop.transitionSpeed, prop.transitionEasingJump);
        }
      });

…省略…

      function changeCurrent(){

      var _win = getWindowInfo(),
      _cn = getColumnContainerInfo(),
      _pos = -_cn.left - _win.w / 2, // 現在の横スクロール位置から、各カラム領域の半分位をマイナスした地点を基準に、メニューの現在地を判定する
      _dp = prop.dataPossessor,
      _$nav = $(prop.mainNavigation).find('li'),
      _cur = replaceString(prop.currentSelector);

      for(var i = 0; i < _dp.length; i++) {
        if( _pos <= _dp&#91;i&#93;){
          var _id = '#' + $(prop.columnContainerItem).eq(i).attr('id'),
          _$cur = $(prop.mainNavigation).find('a&#91;href="'+_id+'"&#93;');
          if(_$cur.length > 0){
            _$nav.removeClass(_cur);
            _$cur.parent('li').addClass(_cur);
          }
          return;
        }
      }
    }

書籍のほうでは実際にコンテンツを入れたサンプルも掲載しております。

・横スクロールするページ(コンテンツサンプル)
http://www.html5-memo.com/sample/jq-books/14/sample_layout.html

今回は以上になります。

今回はアプリ風インターフェイスのフォトギャラリーを作成します。

この記事を書いた人

著者 : ハヤシユタカ

2001年、有限会社ムーニーワークスを設立。WEB制作の他、書籍執筆、セミナー講演、企業研修などを行う。また、クリエイター育成機関デジタルハリウッドでは1999年より講師として本科WEBデザイナーコースやデジタルデザインコースを担当。 詳しいプロフィールはこちら

最近書いた記事

この記事に関連する記事