WebDesign Dackel

jQueryでプラグインを使わずに、マウス位置によるスクロールアニメーションを実装してみる

jQueryでプラグインを使わずに、マウス位置によるスクロールアニメーションを実装してみる

Hatena0
Google+0
Pocket0
Feedly0

今回は、よくFlashサイトで見られた演出でマウスの位置によってスクロールするUIをプラグインを使わずに実装してみようかと思います。
こういったUIを作るためのUIはすでに出ていていますので、無理に自力で実装する必要もないかなぁと思いますが、基本的な作り方を覚えておけば、動作やアニメーションのカスタマイズにも幅が広がります!

DEMO

では、まずDEMOを用意してみたので、動作を確認してみて下さい。
今回は、写真ギャラリーを想定して実装を進めて見たいと思います。

jQuery – マウス位置によるスクロール | DEMO

仕様

動作の仕様は以下の様な感じでいきたいと思います。

  • マウスが乗っていない時は等速度で移動する
  • マウスが乗ったら、マウスの位置によって移動速度、方向が変わる
  • 左右の端までアニメーションしたら移動が止まる (今回はループ無し)

対応するブラウザはIE7〜、Chrome、Firefox、Safari、の主要なブラウザです。

HTML,CSSを用意

必要最低限のHTMLで用意するのは、<ul>を使ったリスト、それを囲んだ<div>です。

<div class="msContainer">
    <ul class="msItemWrapper">
        <li class="msItem"><img src="images/img1.jpg" alt=""></li>
        …
        <li class="msItem"><img src="images/img19.jpg" alt=""></li>
    </ul>
</div>

CSSは以下です。

.msContainer {
  position:relative;/*for IE7*/
  overflow:hidden;
  width:940px;
  padding:10px;
  }

.msItemWrapper {
  position:relative;
  overflow:hidden;
  width:1000%;/*適当に大きな値を入れておく*/
  }

.msItem {
  float:left;
  padding-right:5px;
  }

CSSのポイントは、<ul>に対して大きめの値を設定しておくことくらいです。後は普通に横並びのリストを作ります。
※装飾用のスタイルは外しています。

動作の基本を理解する

動作の基本を理解する

スクロールの考え方は簡単で、overflow:hiddenを書けた<div>の中で<ul>を移動させることで実現します。
そして、今回の肝であるマウス位置によって移動速度を変えるためには、mousemovemouseleavesetIntervalを使います。

mousemoveでは、マウスが要素内のどの位置にいるかを逐一取得して、移動速度を計算します。
mouseleave(Flashで言うmouseout)では、マウスが離れた際にデフォルトの移動速度へ戻します。
setIntervalで、mousemove、mouseleaveで取得した移動速度を反映して実際にアニメーションさせます。

早速JSの変数を定義

必要な変数を用意していきます。

var $container = $(".msContainer"), //リストのコンテナ
    $itemWrapper = $(".msItemWrapper", $container), //リストの親要素
    $item = $(".msItem", $itemWrapper); //それぞれの要素

var FPS = 1000 / 60 >> 0, //FPS (60にある数字が大きい程滑らかに動くけど、重たくなる)
    timerID = null; //タイマー変数

var cw = $container.width(), //コンテナの横幅
    cl = $container.offset().left, //コンテナのページ内の座標
    xe = 0; //右端の終点
    vx = 0, //X移動量
    mx = 0, //X移動量
    per = 0; //マウス位置のパーセント

var maxSpd = 12, //移動速度の最大値
    defaultSpd = 1, //移動速度のデフォルト値
    spd = defaultSpd; //現在の移動速度

初期化する

function initiazlie(){
  ...
}

// コンテンツの読み込みが完了したら初期化を実行 (画像サイズが取れなくなることを防ぐ)
$(window).load(function(){
  initialize();
});

今回は画像要素の幅を取得するので、$(function(){});ではなく、window.onloadを使って要素の読み込みが完了した時点で初期化を実行していきます。
initialize() の中身を実装していきます。

// 初期化
function initialize(){
  // リストのラッパーサイズを確定します
  $itemWrapper.width( $item.outerWidth() * $item.size() );

  // ラッパーのサイズが確定したら、右端の終点位置を計算します
  xe = ( $itemWrapper.outerWidth() - cw ) * -1;

  // 各イベントを発行します
  $container
    .bind("mousemove", onMouseMove)
    .bind("mouseleave", onMouseOut);

  // タイマースタート
  timerID = setInterval(onEnterFrame, FPS);
}

CSSで大きめに指定しておいた<ul>タグのwidthを内容によって合わせておきます。
そうすることで、スクロールしていった時に動きを止める右端の座標を計算します。

Mousemoveを実装

initialize()で設定したonMouseMove()を実装します。
ここで、マウス座標によって移動する方向、速度を計算します。

// マウスムーブ
function onMouseMove(e){
  vx = e.clientX - cl;
  per = Math.floor( (vx / cw) * 100 );
  per = ( per / 50 ) - 1;//ポイント!
  spd = maxSpd * per;
}

引数にあるeventオブジェクトの「clientX」にページ中のマウス位置が入っているので、その値から<div>のページ中の座標を引くことで現在のマウス位置を要素に対して相対的に求めます。(<div>の左端が0)
次に、そのマウスの座標が要素の何%の位置にいるかを求めます。
そしてポイントは、中心が左右それぞれの0%となり、左端が100%、右端が100%になるように、計算しなおします。
求めた%を最高速度に適用してマウスの位置から移動速度を求めます。

Mouseleaveを実装

マウスが離れた際にはデフォルトの移動速度へ戻します。とってもシンプルです。

// マウスアウト
function onMouseOut(e){
  spd = defaultSpd;
}

setIntervalを実装

FlashでいうonEnterFrameを実装していきます。ここでは、上記のmousemovemouseleaveで求めた値実際に<ul>要素の座標へと反映します。
また、左端、右端からはみ出していないかのチェックを行います。

// エンターフレーム
function onEnterFrame(){
  mx = mx - spd;
  // 左端を超えたら
  if( mx > 0 ){
    mx = 0;
  // 右端を超えたら
  }else if( mx < xe ){
    mx = xe;
  }
  // 計算した移動値を反映します
  $itemWrapper.css("left", mx);
}

一応上記の実装で、ひと通りの実装が終わりました。
作る前は地味に大変なんじゃないかなぁと思っていたので、思ったよりは短いコードで実装できました。

JSコード全体

実装したJSのコードは以下のとおりです。

$(function(){

    var $container = $(".msContainer"), //リストのコンテナ
    $itemWrapper = $(".msItemWrapper", $container), //リストの親要素
    $item = $(".msItem", $itemWrapper); //それぞれの要素

    var FPS = 1000 / 60 >> 0, //FPS (60にある数字が大きい程滑らかに動くけど、重たくなる)
    timerID = null; //タイマー変数

    var cw = $container.width(), //コンテナの横幅
    cl = $container.offset().left, //コンテナのページ内の座標
    xe = 0; //右端の終点
    vx = 0, //X移動量
    mx = 0, //X移動量
    per = 0; //マウス位置のパーセント

    var maxSpd = 12, //移動速度の最大値
    defaultSpd = 1, //移動速度のデフォルト値
    spd = defaultSpd; //現在の移動速度

    // 初期化
    function initialize(){

        // リストのラッパーサイズを確定します
        $itemWrapper.width( $item.outerWidth() * $item.size() );

        // ラッパーのサイズが確定したら、右端の終点位置を計算します
        xe = ( $itemWrapper.outerWidth() - cw ) * -1;

        // 各イベントを発行します
        $container
        .bind("mousemove", onMouseMove)
        .bind("mouseleave", onMouseOut);

        // タイマースタート
        timerID = setInterval(onEnterFrame, FPS);

    }

    // マウスムーブ
    function onMouseMove(e){
        vx = e.clientX - cl;
        per = Math.floor( (vx / cw) * 100 );
        per = ( per / 50 ) - 1;
        spd = maxSpd * per;
    }

    // マウスアウト
    function onMouseOut(e){
        spd = defaultSpd;
    }

    // エンターフレーム
    function onEnterFrame(){
        mx = mx - spd;

        // 左端を超えたら
        if( mx > 0 ){
            mx = 0;

        // 右端を超えたら
        }else if( mx < xe ){
            mx = xe;
        }

        // 計算した移動値を反映します
        $itemWrapper.css("left", mx);
    }

    // コンテンツの読み込みが完了したら初期化を実行 (画像サイズが取れなくなることを防ぐ)
    $(window).load(function(){
        initialize();
    });

});

アニメーションの基本的な動作を知っていれば、プラグインを使わずにオリジナルな動きをどんどん取り入れられるのではないでしょうか?
例えばこれをカスタマイズして、アニメーションに慣性を付けて滑らかに動かしたり、無限ループするようにしたり、最初に等速度の移動は切ってみたり、他のエフェクトと組み合わせた見たり…

何となくプラグインに頼ってUIを作ってきたけど、オリジナルの動きをデザインしたい、動作をさせたいといった方の参考になればと思います。
もっとこう書いた方が軽いよ!とか、そもそも動かねーよ!といったご意見ありましたら、コメントいただければとっても嬉しいです!