スライドショーをJavaScriptで自作してみる(コピペで使えます)

ホームページ作りを学んでいるうえで、まぁよく見るファンクションで

スライドショーがある。

よく見るファンクションなのでJQueryのライブラリなんかも充実してて、実装しようと思えがそう言うの使えば楽勝なんだけど、そうじゃなくて自力で作れるようにならんとな!ともって自力で作った記録。

今回作ったのは普通に1枚ずつスライドするのじゃなくて、左右の「ネクストスライド」がちょっと見えてるような作りを目指すことにした。

画像で見せるとこんな感じ。

左右の写真が見えてて、それに暗転がかかってる感じ。これを作るまでの思考プロセスなんかも記録していきます。

気の早い方はまずデモでも見て下さいな。

デモサンプル

スライドショーのファンクションを決める

まずはスライドショーの設計をどうするかを考えました。ちなみにアイデア段階ではgoogle先生の力を借りずに、ギミックを考えだすと言う作業をしてみました。

最初に考えるのは当然、写真が3枚あって、左右に移動する事を考えます。まぁ当然ですよね。

スライドショーなので横に「ニュー」っと動いてほしいので、その機能も考えると、例えば右にスライドしていったら、左の写真が中央に来て、右の写真は画面外に出た後、また左側に現れないといけませんね。

うーん、そのギミック地味に難しそうだな。だって、普通に右にずらした後に左に移動させていかなきゃいかんのだろ?と言う疑問にぶち当たります。

例えばこの時点では

<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>

みたいな感じでそれが

<div class="box3"></div>
<div class="box1"></div>
<div class="box2"></div>

こうなって行くような設計を考えました。

でも左のを右にするのはcssでtransoformと付けときゃ楽勝だろうけど右のを左にもっていって、なおかつスムーズに左から右に流す。って言う流れを考えると、普通にクラスを付け替えただけじゃうまくいかんだろうな。と言う事に気が付きます。

だって右のが左に移動する動きが見えちまう可能性が高いからです。
opacity: 0;
とかで見せなくすると、今度はまた見えるようにするクラスを時間差で入れるとか、しないといかんだろうし、意外と面倒くさそうだな。なんて考えていくうちに、ちょっと思い切ったアイデアを思いつきます。

写真を5枚ならべちゃえ!

んなら、もともと左スライドのさらに左にもあらかじめスライドを隠しておく、右もしかり。

つまり5枚の写真を並べて置けばスムーズなんじゃないか?って言うアイデアを思いつきます。

例えば一番右に1つずらした時には左の写真が中央に、中央の写真が右に行くわけですが、この時にあいちゃう左の写真には「さらに左」に隠れていた奴が出てくればいい!

んでもって、一番右に隠れてたやつはこっそり一番左に回ってくればいい

と言うギミックを思いつきます。

絵で言うとこんな感じ。

2番が真ん中の写真で、つまりいま見てる写真。1番と3番が見きれてる左右の写真です。

そのさらに左右に0番と4番が控えてる感じでこの2枚はまったく見えない位置です。

このまま右に1つスライドすると

1番が中央へ 0番と2番が左右 4番と3番がさらに外側

になる感じです。これなら一番右の4番が、一番左に移動する事を隠すのが簡単です!だって見えないとこから見えないとこに移動するだけですから

と言う事でこのギミック自体は成立しやすい!と言う思いに至り、これを作ってみよう!と言う事になりました。

まずはHTMLとCSSでマークアップしていこう

設計が決まったので、それに合わせてまずはHTMLとCSSで形を作る事にしました。

スライドを表示する枠が必要だな。

左右の見切れちゃうところは

overflow: hidden;

当たりで上手い事隠せるだろ?なんて考えながら設計。

ちなみにだが、このスライドショーの設計の致命的な欠陥は、最低5枚のスライドが必要なってしまう点だろう!

とは言え、左右に何かある時点で最低でも3枚は入れる人しか使わないわけなので、

3枚の時は同じスライドを用意して6枚

4枚の時は8枚

5枚以上の場合はどうにでもなるだろう。

と言う強引な考え方の元推し進める事にした。まぁそれでも最終的には実用的な物が出来上がるので、決して悪くない考えだったとは思う。

結果としてスライド部分のHTMLはこんな感じになる。

なんでとりあえず、実験用に作ったHTMLなので、スライド枚数は8枚で、それぞれのスライドに色を付けてみてる。

backgroun-imageとかで写真を設定すれば写真にもなるけど、デモはこの方が見やすかったりもするので、見栄えはともかく試験をするにはこれでいいだろうと、このまま初めて、ギミックを考えつつCSSも同時進行で構築した結果が

こんな感じになっている。

要するに「id=slideshow」のdivタグの中に横に写真を5枚広げる。

真ん中は全部見えるし左右はちょっとだけ見えるようにしとこう。さらに外側に見えない1枚がこっそり忍ばされてるよ!って言う事です。

スライド8枚の場合は3枚に関してはclassにnoboxと言う物を作っておいてdisplay: none;で隠してしまおう!って言う意図が見て取れると思います。

さぁ後はJavaScriptでコードを書くだけだ!

使いまわせるプログラムにしよう(コピペで使えるよ)

コードを書くにあたって、せっかく作るんだから今後も使いまわせるプログラムにはしていこうと思ったので、強引に書き進めるんじゃなくて、細かくファンクション化して1つ1つを作っていこうと言う意思を固めておきます。

きっとプロのエンジニアからしたら当たり前なんだろうが、その辺はまだまだ初心者エンジニアである。

動かす軸を決めて、それを見つける!

コードを書く前にスライドの起点を決めないとやり辛いので、今回はclassで「box0」と言うクラスが何においても起点になるように設計する事にした。

単純にindex番号の先頭を起点にした方がやり易いだろう

と言う考えの元に決定された考えで、まぁ間違いなくやり易いであろう方法である。そのうえで

box0 = 画面外の一番左のスライドで見えない奴!

と言う場所も決めて書くことにした。

今回はスライドショーになる要素はdivタグでその中でも「img_box」と言うクラスが付いた物をスライドと認識するように

const imgBoxes = document.querySelectorAll('.img_box');

でimg_boxクラスを取得する事にした。のちに「imgBoxes.length」と設定する事で、スライド枚数の変化にも対応しやすいように考えてある。

次にしなくてはいけないのは「box0」クラスが付いているのは、imgBoxesのindex番号何番目かを把握する事である。

imgBoxes.indexof(' box0 ');

ぐらいのコードで取得できんだろ。と軽い気持ちで挑んだら、あれ?「-1」って言う早い話が「false」しか返ってこないから、まずつまずくわけですよ。

色々ググった結果、どうやらquerySlectorAllで取得した物はNodeListと言う物らしく、そのままではサクッと検索する事が出来ないらしい。

 // box0を持つ配列の番号を取得
function indexFinder() {
    let findBoxes = Array.from(imgBoxes);
    let finder = [];

    for (let i = 0; i < findBoxes.length; i++) {
      let find = findBoxes[i];
      finder.push(find.classList.contains(`box0`));
    }

    let findBox = finder.indexOf(true);
    return findBox;
  }

ちゅうことで構築されたfunctionがここ。

一旦、imgBoxesを配列に変換するのが一番上の部分。そこから「box0」を持つ要素がどこなのかindex番号で取得するのが目的なので

let finder = [];

    for (let i = 0; i < findBoxes.length; i++) {
      let find = findBoxes[i];
      finder.push(find.classList.contains(`box0`));
    }

配列1つ1つに「box0」があるかどうかを「true」と「false」で返してもらって、それをfinder変数にpushする事で「true」と「false」だけの配列を作り、その上で「true」が何番目にあるのかをreturnしてらえばいいのである!

こうして、無事「box0」が何番目にあるのか取得できたので、その辺は変数にしまっておく

  // box0を持つ要素を取得しておく
  let master0 = indexFinder();
  let hold_box0 = indexFinder();

なんで2つ?なんで「let」と言う事はこの後の事情でそのうちわかる思う。master0は常に動いてほしくないので設定した変数で、hold_box0はかなり動的に使う変数に後々なっていく。

まずはスライド番号のclassを全部消す

無事「box0」のありかが取得できるようになったので、この後は今ついてるスライド番号を把握するクラス「box0」~「box4」それから非表示に「nobox」くらすをいったん全部消して、その後に一つずらして新しく付与すればいいんだろ?

と考えたので、クラスを全部消すことに専念した。

remover() ファンクションを作り出すのは意外と面倒くさかった。

no no 0 1 2 3 4 no

ぐらいの並びの時はいいが

2 3 4 no no no 0 1

見ないな並びの時も出てくるからその辺りはif構文とかで色々設定しなくちゃいけなかったので、頭の中で色々条件式を書いてはconsoleで確認して・・・

ちきしょー!等符号逆じゃねーか!!

とか微妙な調整をしつつ完成したのが

// クラスを削除するファンクション
//いったんboxクラスをすべて消す
function remover() {
    for (let i = 0; i < 5; i++) {
      if (hold_box0 > imgBoxes.length - 1) {
        hold_box0 = 0;
      }
      imgBoxes[hold_box0].classList.remove(`box${i}`)
      hold_box0++;
    }

    hold_box0 = master0;

    for (let i = 0; i < nobox.length; i++) {
      if (hold_box0 - 1 < 0) {
        hold_box0 = imgBoxes.length;
        imgBoxes[hold_box0 - 1].classList.remove("nobox");
        hold_box0--;
      } else {
        imgBoxes[hold_box0 - 1].classList.remove("nobox");
        hold_box0--;
      }
    }
  }

この辺のファンクションである。

普通のboxは結構消すのが簡単だったのだが、noboxは意外と苦労した。と言うのも「hold_box0」を動的に使い過ぎたので、起点がずれちゃうのである。そのために「master0」と言う変数で再度上書き出来るように用意しておく事でそれを回避する事にしました。(なんかもっといいやり方があったんだけど思い出せなかった)

色々微調整しつつ、スライドショーの数が変わっても無事すべてのクラスを削除できるファンクションが出来上がりました。

次は1つずらしてclassを付与!

無事消せたので、次はクラスを付与してやらなくちゃいけません!

// クラスを付与するファンクション
// box0の位置を把握しておくことが大事!
function additional() {
    for (let i = 0; i < 5; i++) {
      if (hold_box0 > imgBoxes.length - 1) {
        hold_box0 = 0;
      }
      imgBoxes[hold_box0].classList.add(`box${i}`);
      hold_box0++;
    }

    hold_box0 = master0;

    for (let i = 0; i < imgBoxes.length - 5; i++) {
      if (hold_box0 === imgBoxes.length - 1) {
        imgBoxes[hold_box0 - 1].classList.add('nobox');
        hold_box0--;
      } else if (hold_box0 === 0) {
        hold_box0 = imgBoxes.length - 1;
        imgBoxes[hold_box0].classList.add('nobox');
      } else {
        imgBoxes[hold_box0 - 1].classList.add('nobox');
        hold_box0--;
      }
    }
  }

ここではまずただ単に、classを消す前の事は考えずに「box0」から順番に「box4」までを付与して、余剰なスライドには「nobox」を付ける為のファンクションを構築します。

起点になるのは「master0」に入ってる番号を元に作られた「hold_box0」なわけです。

あれ?でもこれだと、さっきの場所にまた「box0」が付いてスライドしないのでは?と言う事に気が付いている方は、ちゃんとプログラム読めてます!

今はとりあえず順番に付与できればそれでいいんです!ファンクションさえ作っちゃえば後で操作できるから、何とかなるんです!!

さていよいよ実行だ

ファンクションは出来たので、次は実行する為のファンクションを構築します。

それがこの部分。右にスライドさせる時と、左にスライドさせる時で別々のファンクションになってます。

 // 右にスライドするファンクション
  function rightMove() {
    remover();
    master0--;
    if (master0 < 0) {
      master0 = imgBoxes.length - 1;
    }
    hold_box0 = master0;
    additional();
    hold_box0 = master0;
  }

  // 左にスライドするファンクション
  function leftMove() {
    remover();
    master0++;
    if (master0 > imgBoxes.length - 1) {
      master0 = 0;
    }
    hold_box0 = master0;
    additional();
    hold_box0 = master0;
  }

そう!ここでさっきの疑問を解決しています。

クラスを付与するファンクション「additional]を実行する前に、「master0」の変数を1つずらいちゃうんです。
ここでも当然端っこに0が来たときのループも設定する必要があるので、その辺りだけif構文でかかえれていますが、いたってシンプルな構造です。

後は実際に動的に動く変数である「hold_box0」を適宜「master0」で初期化してやれば動く!って言うう流れです。

最終的にはこんな感じ

で、出来上がったコードとその動作サンプルがこんな感じです。

デモサンプル

上手くできたのでよかった

デモをい見ればわかる通り、無事に希望していたギミックが完成しました!きっともっと簡単なコードもあるんだろうけど、脱JQueryを目指す身としてはなかなかにいい感じの出来栄えである。コピペで使いまわせるのもとってもいい仕上がりだ!

特に著作権とかは主張するつもりもなのでご自由にお使いください。

ちなみに最初の方に書いてあるプログラムで

const slideshow = document.getElementById('slideshow');

っと言う何も使わなかった変数があるが、別に間違いじゃなくて、今後の拡張を考えているだけである。もう1つ増やしたいファンクションがあるので、実装出来たらまたブログにアップします!