Webサイトでは必ずと言っていいほどよく見かけるスライダーUIですが、どういった仕組みで動いているか知っていますか?
いまや便利で高機能なjQueryプラグインがたくさんあるため、わざわざイチから作る機会は少ないと思います。しかし、仕組みを知っておけばプラグインで対応できない場合に作ることができますし、プラグインをカスタマイズして実装することもできるようになります。
自分で作ることができるようになると仕様をプラグインに縛られることがなくなるため、わがままな要求にも応えられるようになります。なにより自分で作って動いてるものを見ると楽しいこと間違いなしです。
そういったものでバグが見つかったときの焦り具合も尋常じゃありませんが。
ということで、スライダーの作り方と仕組みを初心者向けに解説していきたいと思います。
(ここでいう初心者とはHTML・CSS・jQueryの基本がわかる程度です)
※IE8〜11、Safari(Mac)、Firefox、Chromeで動作確認しています。
今回作るスライダーは・・・はい、これです。
よく見る形だと思います。
先に仕組みをひとことで言っておくとfloat(*1)で横に並べた要素を左右にずらしているだけです。
簡単そうに言ってるからなのか、すごく簡単そうですね!
- *1…floatである必要はなく、とにかく横に並べばいいです。
環境設定
解説するにあたってテンプレートを作って環境を合わせたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>jQueryを使ったスライダー</title> <style type="text/css"> /* ここにスタイルシート */ </style> <script src="jquery-1.11.3.min.js"></script> </head> <body> <!-- ここにHTML --> <!-- ここにjQuery --> </body> </html> |
slider.htmlという名前で保存し、jQueryは公式サイトからダウンロードしてslider.htmlと同じフォルダに入れておいてください。
ブラウザはGoogle Chromeを使いますが、FirefoxでもSafariでも大丈夫です。
HTMLとCSSを作る
jQueryによるスライダーということでjQueryの話しがメインになるかと思いきや実は違います。
スライダーUI以外にも言えることですが、HTMLとCSSをしっかり組むことが大事です。
それをjQueryで動かすだけ。jQueryは添えるだけ。
jQueryは添えるだけ。
・
・
・
ということで、添えるだけのjQueryは一旦忘れてHTMLで次のような構造を作りましょう。
1 2 3 4 5 6 7 8 9 |
<div class="slider"> <div class="slideSet"> <div class="slide">slide1</div> <div class="slide">slide2</div> <div class="slide">slide3</div> <div class="slide">slide4</div> <div class="slide">slide5</div> </div> </div> |
コードを見ると3種類のクラスがついたdivがあることがわかると思います。
このdivは、それぞれ
- 画像やテキストなどのコンテンツを入れるdiv.slide
- それを格納して移動するdiv.slideSet
- 表示領域を制御するdiv.slider
といった役割を持ちます。
この構造と役割がとても大事です。覚えておいてください。
次にこの状態でスライダーのように見せるためのスタイルシートを作ります。
まだjQueryは使いません。
スタイルシートではdiv.slideを横に並べ、div.slideSetの幅を広げ、div.sliderでスライダー全体の幅をdiv.slideと同じにしたうえではみ出した部分を非表示にするといったことを行います。
ここではサイズ500×200pxの画像を5つ並べるという想定でスタイルシートを作っていきますが、実際にはスライダーに使う画像やコンテンツのサイズに合わせて変更してください。
まずはdiv.slideを横に並べます。floatを使いましょう。
あと実際には必要ありませんが、ここではdiv.slideの境界がわかるように線をつけておきます。
1 2 3 4 5 6 |
.slider .slide { width: 498px; height: 198px; border: 1px solid #f00; float: left; } |
続いてdiv.slideSetの幅を広げます。
横一直線に並べるのでカラム落ちしないようにdiv.slideSetに「div.slideの幅×div.slideの数」の幅を指定します。
1 2 3 |
.slider .slideSet { width: 2500px; } |
一度ここでプレビューしてみましょう。
うまく出来ていればdiv.slideが横に並び、ウィンドウを突き抜けていると思います。
もしdiv.slideがカラム落ちして2段になっている場合は、div.slideSetの幅が足りてないはずですので、もう一度「div.slideの幅×div.slideの数」で数値を計算してみてください。
このままでは余計なスライドまで見えていますので、div.sliderのサイズを指定してoverflow: hiddenではみ出した部分を非表示にしてしまいましょう。
1 2 3 4 5 |
.slider { width: 500px; height: 200px; overflow: hidden; } |
再びプレビューしてみましょう。
slide1だけが表示されていると思います。
これでスライダーのHTMLとCSSが出来上がりました。
図にして整理すると・・・
こういう構造になっています。
さて、実はこのままでは問題が1つあります。
それはdiv.slideSetの幅を計算してスタイルシートで指定している部分です。
実際にはdiv.slideの数は増減しますし、幅も変わるはずです。ですので、ここはdiv.slideの数と幅に合わせて自動的に計算された幅が指定されなければいけません。
ここからいよいよjQueryの出番です。
div.slideSetの幅をjQueryを使って計算して指定する
「jQueryを使って計算」と書くのはちょっと誤解を生むかもしれません。実際は「jQueryでdiv.slideの数と幅を取得してJavaScriptで計算してjQueryでスタイルシートを指定する」ということなんですが、使い分けて書くのは煩わしく、読みにくいのでjQueryということで統一します。
ちなみにjQueryはJavaScriptライブラリのひとつでjQueryを使っても裏側で動いてるものはJavaScriptです。ここを間違えると恥ずかしいので気をつけましょう(気をつけます)。
それではとりあえず、jQueryで幅と数を取得してそれを変数に入れるコードを書いてみます。
1 2 |
var slideWidth = $('.slide').outerWidth(); // .slideの幅を取得して代入 var slideNum = $('.slide').length; // .slideの数を取得して代入 |
$(selector)で.slideを参照、outerWidth()で幅、lengthで数を取得し、それぞれslideWidthとslideNumという変数に代入しています。
このslideWidthとslideNumを掛け算した値がdiv.slideSetの幅になるわけですね。
ちなみに、outerWidth()を使っているのはdiv.slideにborderが指定されているためです。width()ではborder分が加算された幅を取得できず計算が合わなくなります。div.slideにborderやpaddingが指定されていない場合にはwidth()でも大丈夫です。
ではslideWidth * slideNumで計算した値をdiv.slideSetに指定しましょう。
1 2 |
var slideSetWidth = slideWidth * slideNum; // .slideの幅×数で求めた値を代入 $('.slideSet').css('width', slideSetWidth); // .slideSetのスタイルシートにwidth: slideSetWidthを指定 |
変数slideSetWidthにslideWidth * slideNumの値を代入し、$(selector)で.slideSetを参照しスタイルシートのwidthにslideSetWidthを設定する。という内容です。
これでプレビューしてChrome(じゃなくてもいいです)のDeveloper Toolsを開いてwidthが指定されているか見てみましょう。
Developer ToolsのElemetnsタブでdiv.slideSetにwidthが指定されていれば正しく動作しています。
もし指定されていない場合は、jQueryを読み込めていないかコードにミスタイプがあると思いますので確認してみてください。
うまくできたらstyle要素でdiv.slideSetに指定しているwidth: 2500pxは不要になるので削除してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<style type="text/css"> .slider { width: 500px; height: 200px; overflow: hidden; } .slideSet { width: 2500px; /* 削除 */ } .slide { width: 498px; height: 198px; border: 1px solid #f00; float: left; } </style> |
これでスライダーの形ができました!
これを今度は動かしていきましょう。
jQueryでアニメーションを実行する
アニメーションはスタイルシートを連続で更新してHTML要素を動かすことによって表現します。
その動きをjQueryではanimate()を使って簡単に作ることができます。
とりあえずアニメーションさせてみましょう。動かす対象はdiv.slideSetです。
この要素の位置が左にdiv.slideの幅分動けばslide1からslide2へスライドして見えるわけですね。
ということでこのようなコードになります。
1 2 3 4 5 6 7 8 9 |
var slideWidth = $('.slide').outerWidth(); // .slideの幅を取得して代入 var slideNum = $('.slide').length - 1; // .slideの数を取得して代入 var slideSetWidth = slideWidth * slideNum; // .slideの幅×数で求めた値を代入 $('.slideSet').css('width', slideSetWidth); // .slideSetのスタイルシートにwidth: slideSetWidthを指定 // アニメーションの実行 $('.slideSet').animate({ left: -slideWidth }); |
これでアニメーションが実行されるかと思いきや実はされません。
プレビューしてみましょう。
ただ、Developer Toolsで見てみるとdiv.slideSetのleftが高速で書き換えられているのは確認できます。
jQueryはうまく動作しているわけですね。
この原因は単純で、leftが指定されている要素にposition: absoluteが指定されていないからです。
ということで、div.slideSetにposition: absoluteを指定すればいいわけですが、それだけだとdiv.slideSetの親がbody要素になっているため、位置がおかしくなります。
また、overflow: hiddenではみ出しているスライドを非表示にしていてもposition: absoluteが指定された子要素は非表示になりません。
以上のことを対策するとdiv.sliderとdiv.slideSetのスタイルシートはこうなります。
1 2 3 4 5 6 7 8 9 10 |
.slider { width: 500px; height: 200px; overflow: hidden; position: relative; } .slider .slideSet { position: absolute; } |
再びプレビューしてみましょう。
ちょっとわかりにくいですが、さっきまでslide1があった位置にslide2がアニメーションして移動してきました。
ちなみにposition: absoluteではなくposition: relativeやmargin-leftでも同じことができますが、position: absoluteの方が処理が軽くなります。
また、transformが使える条件であればtransformの方がスムーズにアニメーションして見えます。
さて、いまは勝手にslide2へスライドしてしまいますので、これを「次へ」ボタンが押されたときに実行するようにしてみましょう。
スライドアニメーションを実行するボタンを作る
ボタンはなんでもいいのですが、ここではbutton要素を使います。
あとで前へ戻るボタンも必要になるのでbutton.slider-nextとしましょう。
1 2 3 4 5 6 7 8 9 10 |
<div class="slider"> <div class="slideSet"> <div class="slide">slide1</div> <div class="slide">slide2</div> <div class="slide">slide3</div> <div class="slide">slide4</div> <div class="slide">slide5</div> </div> </div> <button class="slider-next">次へ</button> |
このbutton.slider-nextがクリックされたときに先ほどのアニメーションが実行されればいいわけですね。
jQueryで特定の要素がクリックされたときのイベントを作るにはclick()を使います。
このclick()の第一引数にはクリックされたときに実行するコードを登録できます。さきほどのアニメーションするコードをそのまま入れてもいいのですが、あのコードは「戻る」ボタンがクリックされたときにも使いたいと思いますので、独自関数を作ってそれを実行させましょう。
1 2 3 4 5 6 7 8 9 10 11 |
// アニメーションを実行する独自関数 var sliding = function(){ $('.slideSet').animate({ left: -slideWidth }); } // 次へボタンが押されたとき $('.slider-next').click(function(){ sliding(); }); |
プレビューしてみましょう。
「次へ」ボタンを押すと・・・動きましたね!
さてさて、いよいよスライダーっぽくなってきました。むしろ動きだけ見ればもはやスライダーといっても過言ではありません。
このスライダーを真のスライダーにするためにいまの問題点を挙げてみましょう。
- slide2へはいけるがslide3〜5へいけない
- というか、slide1にも戻れない
といったところでしょうか。
現在地を示すナビゲーションも欲しいところですが、それはまたの機会に。
さて、それではこの問題を解決していきましょう。
スライドする位置を計算してアニメーション実行する
いまのコードではアニメーションする位置の指定がleft: -slideWidthとなっているため何をしても同じ位置にしか移動しません。
このleftの位置を計算して指定するわけですが、計算式としては「移動したいスライドの番号 * -スライドの幅」となります。
ここで注意点がひとつあります。1番目のスライドの正しい位置はleft: 0ですので、実際には1番目は0番目として考えなければいけません。
この計算式の中で「移動したいスライドの番号」を知る方法がいまはありません。この番号を知るためには、まず現在地を知らなければいけません。
そのために現在地を記録しておく変数を用意します。この変数は「次へ」がクリックされたときに+1され、「前へ」がクリックされたときには-1されるものです。これによって次に移動するスライドの番号がわかるという仕組みです。
この現在地=移動したいスライドの番号というわけですね。
スライドの番号がわかれば、あとは先ほどの計算式に当てはめてleftの値を求めることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var slideCurrent = 0; // 現在地を示す変数 // アニメーションを実行する独自関数 var sliding = function(){ $('.slideSet').animate({ left: slideCurrent * -slideWidth }); } // 次へボタンが押されたとき $('.slider-next').click(function(){ slideCurrent++; sliding(); }); |
はじめの位置はSlide1になるので変数slideCurrentには0を代入しています。
.slider-nextがクリックされたときにslideCurrentに1を加算しアニメーションを実行する関数を実行します。この関数の中では「slideCurrent * -slideWidth」という計算式によってleftの値を求めて指定しています。
プレビューしてみましょう。
Slide1から5まで動きましたね!そしてクリックし続けると通り過ぎますね・・・。
新たなる問題が発生しましたが、とりあえず斜め前あたりの微かに見える程度の位置において「前へ」ボタンを実装を進めましょう。
「次へ」ボタンと同じような形で実装できるので簡単です。
1 2 3 4 5 6 7 8 9 10 11 |
<div class="slider"> <div class="slideSet"> <div class="slide">slide1</div> <div class="slide">slide2</div> <div class="slide">slide3</div> <div class="slide">slide4</div> <div class="slide">slide5</div> </div> </div> <button class="slider-prev">前へ</button> <button class="slider-next">次へ</button> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var slideCurrent = 0; // 現在地を示す変数 // アニメーションを実行する独自関数 var sliding = function(){ $('.slideSet').animate({ left: slideCurrent * -slideWidth }); } // 前へボタンが押されたとき $('.slider-prev').click(function(){ slideCurrent--; sliding(); }); // 次へボタンが押されたとき $('.slider-next').click(function(){ slideCurrent++; sliding(); }); |
再びプレビューしましょう。
慣れないうちは細かくプレビューしないと問題を発見できなくなりますからね。
どうでしょうか。これで次のスライドへも前のスライドへも自由自在に動きましたか?
動かない場合は「前へ」ボタンのクラス名が正しいか、-1されているか確認してみてください。
いよいよ終わりが見えてきました。
先ほど斜め前あたりの微かに見える程度の位置においた問題(クリックし続けるとスライドが彼方へ行ってしまうという問題です)を持ってきて解決しましょう。
スライドをループさせる
この問題は現在地がずっと加算(または減算)されているため、div.slideの数を超えて見えなくなってしまうという現象です。
ですので、現在地に入る値を0〜スライドの数の間で制限してやれば解決することができます。
この制限をする方法としては、
- 0以下になったときに0に戻して処理を止める
- 0以下になったときにスライドの数を代入して最後のスライドに移動させてループさせる
のどちらかがベターです。
スロットルーレットのように一方行にずっとループして進み続けるといったものもできますが、それはまたの機会に。
今回は2番の方法を採用します。
処理の流れとしては、「もしslideCurrentが0未満だったらslideNumの値を代入する。もしくはslideCurrentがslideNumを超えていたら0にする。どちらでもなかったらそのまま処理を実行する」という感じです。
それではコードにしてみましょう。
条件判定をするタイミングとしてはanimate()が実行される直前になるので、独自関数のsliding()の中に書いていきます。
ただし、ここでひとつ注意点があります。slideNumにはlengthで取得したスライドの数「5」が入っていますが、slideCurrentは0から始まっているため、番号がひとつずれている状態です。
なので、slideNumから1を引いてやらなければいけません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// アニメーションを実行する独自関数 var sliding = function(){ // slideCurrentが0以下だったら if( slideCurrent < 0 ){ slideCurrent = slideNum - 1; // slideCurrentがslideNumを超えたら }else if( slideCurrent > slideNum - 1){ // slideCUrrent >= slideNumでも可 slideCurrent = 0; } $('.slideSet').animate({ left: slideCurrent * -slideWidth }); } |
プレビューしてみましょう。
これで基本的なスライダーは完成しましたが、ボタンを高速でクリックするとクリックを止めてもアニメーションが止まらずに再生され続けませんか?
この問題を解決してちょっとだけ完成度を上げてみましょう。
jQueryアニメーションをstop()で制御する
「ボタンを高速でクリックするとクリックを止めてもアニメーションが止まらない」という現象はアニメーションのキューが溜まっているから起こる問題です。
このキューはanimate()が実行される度に発行され、アニメーションが終わった時に削除されます。そのため、アニメーションが終わる前に新たなキューが発行されると1,2,3,4…と溜まっていき、1番のアニメーションが終わったら2番のアニメーションを実行し、それが終わったら3番、更にそれが終わったら4番と連続でアニメーションしてしまうわけです。
これを解決するにはanimate()を実行する前にキューを削除したり、再生されているアニメーションを終わらせてやります。
そのために必要なものがjQueryのstop()です。これはアニメーションを終わらせるための関数で、引数でキューを削除したり、アニメーションの終わり方を現在の再生フレームで終わりとするか、最後の再生フレームで終わりとするか制御することができます。
今回はanimate()が実行される前にアニメーションを終わらせてしまえば、キューが溜まらずクリックを止めた時点でアニメーションも止まるため、引数は特に指定しません。
ということでコードはこちら。メソッドチェーンを使用してanimate()の前に記述します。
1 2 3 |
$('.slideSet').stop().animate({ left: slideCurrent * -slideWidth }); |
プレビューしてみましょう。
高速でクリックして止めてもアニメーションが止まりましたね。
このようにユーザーのアクションによって動作するアニメーションはキューが溜まってしまうため、stop()で制御してあげましょう。
ちなみに、is(‘:animated’)などを使用してアニメーション中はクリックしてもその場で処理を止めて、アニメーションを実行させないという方法もありますが、これはユーザーのアクションに対してレスポンスを返していないため、「反応しない。なんで???」となり困惑しますので、あまりいい方法ではありません。
さて、これで終わりと思いきやまだ終わりません。
確かに動きはできたのですが、使用している変数がグローバル変数になっているため、他のJavaScriptと競合する可能性があります。
これを解決するために即時関数を作って実行します。
即時関数にする
即時関数というのはその名の通り、作ったら直ぐ実行される関数のことです。これによりローカル変数を作れるため他のJavaScriptとの競合を回避することができます。
即時関数は(function(){ //実行するコード }())で簡単に作ることができます。
1 2 3 4 5 6 7 8 9 10 11 |
<script> (function(){ var slideWidth = $('.slide').outerWidth(); // .slideの幅を取得して代入 var slideNum = $('.slide').length; // .slideの数を取得して代入 var slideSetWidth = slideWidth * slideNum; // .slideの幅×数で求めた値を代入 $('.slideSet').css('width', slideSetWidth); // .slideSetのスタイルシートにwidth: slideSetWidthを指定 // 略 }()); </script> |
プレビューしてちゃんと動くか確認してみましょう。
これでスライダーの基本機能が備わったものが完成しました。お疲れ様でした。
あとは画像を入れたりデザインしたり自分なりに調整してみてください。
僕はこんな感じにしてみました。
まとめ
- jQueryを使う前にHTMLとCSSで形を作る。
- コンテンツを入れるdivを作る。
- コンテンツの数×幅の大きさを持つdivを作る。
- overflow: hiddenで見える部分を制御するdivを作る。
- 3のコンテンツの数×幅はjQueryを使って計算して指定する。
- animate()で動かすものはひとつだけ。
- 現在地を知ることでスライドの位置を計算することができる。
- stop()でアニメーションのキューを制御する。
おわりに
初心者向けということでできる限り不要な機能・情報を省いたシンプルな内容になりましたが、どういうふうにスライダーが動いていて、それをどう実現しているのか、スライダーの基礎部分は伝えられたかな。と思います。
今度はこれを元にjQueryプラグイン化したり、機能を追加する記事を書いて見ようと思います。
ではでは ( ͡° ͜ʖ ͡° )ノシ
追記(2015/12/02)
変数名がslidecurrentとなっているところがあったのでslideCurrentに修正しました。
slimeさんご指摘ありがとうございます。
自作スライダーを作る際にとても参考になりました!ありがとございますm(__)m
恐れ入りながら一点、、
【スライドをループさせる】のソースコードで
slideCurrentのところがslidecurrentとCが小文字になっております。
自作でスライダーを作る機会があり、この記事がとてもわかりやすく、プレビューもあって助かりました。素晴らしい記事を残していただきありがとうございました!