【JavaScript】ループするスライドショーをJavaScriptのみで実装した

# 1. はじめに
JavaScriptの勉強を始めて約2週間くらいの人間による備忘録です。
間違い、冗長なコードがあります。
頑張ってんな～。くらいの気持ちで見ていただけると幸いです。


## 1-1. 動作確認
* ボタン押下で画面遷移
* 4秒間ボタン操作がない場合、自動でスライドを遷移する
* スライド時間は0.7秒
* スライド遷移中（0.7秒間）はボタン操作禁止

### 自動画面遷移
時間経過（4秒）で自動画面遷移
![auto.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/4249c402-fcac-a419-5a10-07c9b593f0e6.gif)

### ボタンを押下時の画面遷移
* ボタン押下で、時間処理クリア
* 0.7秒間はクリック禁止
![click.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/29c640e8-5788-dc9e-f0ec-5159588a2cda.gif)


### ボタン押下+自動画面遷移
クリック押下後、4秒間放置で自動で画面遷移していることを確認
![clickaouto.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/1674c23c-3fe4-8030-7106-1791c981f797.gif)




## 1-2.目標
楽天市場のスライドショーを模写しました。

※実装中にどうやら仕様が異なったよう。
カーソル置いたら自動更新止まる仕様になってるやん...
自動遷移の直前で戻るボタン押下したら、
一旦スライドは戻るがすぐに自動遷移するバグなくなってるやん...

https://www.rakuten.co.jp/


## 1-3. 得た知識
* [要素のコピー](#3-1-3-学び)
* [アニメーション終了処理](#3-2-3-学び)
* [CSSからクラス要素の適応（追加）](#3-4-3-学び)
* [CSSからクラス要素の適応（削除）](#3-4-3-学び)
* [X秒後に実行する処理](#3-6-3-学び)
* [タイマー処理を終了する](#3-6-3-学び)
* [クリックした後にイベント実行](#3-7-3-学び)
* [クリックを禁止する](#3-8-3-学び)
* [HTML読込み完了後にイベント実行](#3-9-3-学び)

## 1-4. なぜ、JavaScriptだけなのか
* ループするスライドショーの記事を検索してもjQueryを用いた記事がほとんどだった。
* JavaScriptを勉強し始めたてのため、ライブラリを使わずに実装したかった。

## 1-5. 注意事項
* ドットインストールの有料コンテンツである
以下2つの動画をベースに作成したため、
HTMLとCSSのコードは割愛します。
    * JavaScriptでカルーセルを作ろう
    * JavaScriptでストップウォッチを作ろう
  

# 2.仕組み
## 2-1.ループ処理
### 2-1-1.実態画像をコピーし、前後に追加する

![copy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/735de4d0-f95f-ddd1-3618-f349a28d0d28.png)


#### 手順
1. 　イメージ画像を３枚用意。
1. 　先頭の要素（画像１）をコピーし、最後尾（画像３）の後ろに追加。
1. 　画像１´が追加される。
1. 　最後尾の要素から１つ前（画像３）をコピーし、先頭（画像１）の前に追加。
1. 　画像３´が追加される。

両端の１と３はコピー要素です。（１´、３´とする）

### 2-1-2.コピー要素に遷移した直後、すぐに（0sかけて）実態要素へ遷移

![earlymove.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/e1c14af3-4ac8-8209-d4af-c398f46ba02c.png)


![earlymove.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/d75565ad-caca-415f-8bd1-07a0c49e7359.gif)

今、画像３が表示されている状態です。
画像１´に遷移した直後、すぐに（0sかけて）画像１へ遷移しています。
動画わかりにくっ...すみません...


### 2-1-3.参考文献

https://qiita.com/wintyo/items/a37a197f69aa205297a5

## 2-2.画面表示
### 2-2-1.画像を表示する
![currentIndex.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/90c511a8-0424-8adb-9530-83fed758ee45.png)
#### 手順
1. 　「変数名：currentIndex」という、画像を表示させる参照ポインタを用意。
1. 　ボタン押下で「currentIndex」が増減
1. 　時間経過でで「currentIndex」が増加
1. 　「currentIndex」が示す画像を表示

### 2-2-2.画像の遷移
**ドットインストールの有料コンテンツのため割愛**

### 2-2-3.参考文献

https://dotinstall.com

# 3.実装
## 3-1.要素のコピー

### 3-1-1.やりたいこと
[2-1-1.実態画像をコピーし、前後に追加する](#2-1-1実態画像をコピーし前後に追加する)
を参照

### 3-1-2.コード
```javascript:javascript
  const elementFirstItem = document.querySelectorAll('li')[0];
  const copyElementFirstItem = elementFirstItem.cloneNode(true);
  const setElementLastItem = document.querySelectorAll('li')[slides.length];
  carouselArea.insertBefore(copyElementFirstItem, setElementLastItem);   
  
  const elementLastItem = document.querySelectorAll('li')[slides.length - 2];
  const copyElementLastItem = elementLastItem.cloneNode(true);
  const setElementFirstItem = document.querySelectorAll('li')[0]; 
  carouselArea.insertBefore(copyElementLastItem, setElementFirstItem);
```

### 3-1-3. 学び
:::note
**要素のコピー**
  親要素.insertBefore(コピー, コピー位置);
  ※コピー位置の前にコピーが挿入される
:::

### 3-1-4.参考文献

https://www.ipentec.com/document/javascript-copy-element

<!-- -------------------3-2------------------- -->
## 3-2. 画面遷移フロー(次へ)
### 3-2-1. やりたいこと
![slideflow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/451feb80-314d-67d1-db52-3484ff4e7544.png)


### 3-2-2. コード
```javascript:javascript
  function slideFlowNext() {
    currentIndexNext();     //現在インデックスの更新
    slideTransformNext();   //アニメーションスタイルの適応
    slideNext();            //画面遷移処理
    //=>アニメーション終了処理へ
    carouselArea.addEventListener('transitionend', ()=> {
     if(currentIndex === 4){
        slideFlowNext();
      }
    });
  }
```

### 3-2-3. 学び

:::note
**アニメーション終了処理**
addEventListener：transitionend
※アニメーションが0sの時は効かない
:::


### 3-2-4. 参考文献
https://gray-code.com/javascript/execute-processing-when-css-animation-started-and-ended/

<!-- -------------------3-3------------------- -->
## 3-3. 現在インデックスの更新
### 3-3-1. やりたいこと
「currentIndex」が要素の最後（図１´）に来たら、
実態要素へ「currentIndex」を合わせる。

### 3-3-2. コード
```javascript:javascript
  function currentIndexNext() {
    if(currentIndex >= 4){
     currentIndex = 1;
    }
    else{
      currentIndex++;
    }
  }
```

### 3-3-3. ポイント

:::note
**>=の理由**
「currentIndex」が4(最終ページ)以上になったときでも戻ってくるようにする。
画像遷移中に爆速クリックしたとき「currentIndex」が4以上になった。
後述するが、画面遷移中にクリック禁止処理追加で修正完了
:::

<!-- -------------------3-4------------------- -->
## 3-4. アニメーションスタイルの適応
### 3-4-1. やりたいこと
* クラス要素を用いてスライド遷移時間を適応させる
* スライド遷移時間は0.7秒

**※ドットインストールのベースを独自に変更したためCSSのコードを記載する**

### 3-4-2. コード
```css:CSS
 .transform-on {
  transition: transform .7s;
}

.transform-off {
  transition: transform 0s;
}
```

```javascript:javascript
  function slideTransformNext() {
    if(currentIndex === 1) {
      carouselArea.classList.remove('transform-on');
      carouselArea.classList.add('transform-off');
    }
    else {
      carouselArea.classList.remove('transform-off');
      carouselArea.classList.add('transform-on');
    } 
  }
```
</details>




### 3-4-3. 学び

:::note
**CSSからクラス要素の適応（追加）**
要素.classList.add('クラス');
:::

:::note
**CSSからクラス要素の適応（削除）**
要素.classList.remove('クラス');
:::


### 3-4-4. 参考文献

https://qiita.com/tomokichi_ruby/items/2460c5902d19b81cace5


<!-- -------------------3-5------------------- -->
## 3-5. 画面遷移処理
### 3-5-1. やりたいこと
* 画面を遷移させる

### 3-5-2. コード

:::note warn
**ドットインストールの有料コンテンツのため割愛**
:::

### 3-5-2. 学び

:::note
**画面遷移の仕組み**
:::


### 3-5-3.参考文献

https://dotinstall.com



<!-- -------------------3-6------------------- -->
## 3-6. 自動画面遷移処理
### 3-6-1. やりたいこと
* 4秒後に自動で画面遷移
* 関数コール時、画像遷移中に更新してる時間をキャンセルさせる
* 0.7秒後（画面遷移と同じ時間）経過後、4s計測再開
* 画面遷移フロー(次へ)のコール
* クリック禁止処理

### 3-6-2. コード
```javascript:javascript
const slidePageTime = 700; //ms

function slideAuto() {
    timeCount = setTimeout(() => {
      clearTimeout(timeCount);
      setTimeout(slideAuto,slidePageTime);
      slideFlowNext();
      pointerNoneFlow();
    }, 4000);//ms後に実行 
  }  
```

### 3-6-3. 学び

:::note
**X秒後に実行する処理**
setTimeout(() => {}, 時間);
:::
知らん間に非同期処理に片足突っ込んでた...

:::note
**X秒後に実行する処理**
setTimeout(関数,時間);
:::

:::note
**タイマー処理を終了する**
clearTimeout();
:::

### 3-6-4. ポイント

:::note
画面遷移時間と次回の処理実行時間を合わせることで、
ほぼ同タイミングで計測再開してくれる
:::

### 3-6-5. 参考文献
https://www.javadrive.jp/javascript/function/index16.html

https://techplay.jp/column/548

https://webukatu.com/wordpress/blog/39100/

<!-- -------------------3-7------------------- -->
## 3-7. ボタンを押下したときの処理(次へ)
### 3-7-1. やりたいこと

:::note warn
3-6. 自動画面遷移処理とほぼ同じのため割愛
ボタンに対応させただけ
:::

### 3-7-2. コード
```javascript:javascript
next.addEventListener('click', () => {
    clearTimeout(timeCount);
    setTimeout(slideAuto,slidePageTime);
    slideFlowNext();
    pointerNoneFlow();
  });
```

### 3-7-3. 学び

:::note
**クリックした後にイベント実行**
addEventListener：click
:::


<!-- -------------------3-8------------------- -->
## 3-8. クリック禁止/解除フロー
### 3-8-1. やりたいこと

**※ドットインストールのベースを独自に変更したためCSSのコードを記載する**

### 3-8-2. コード

```css:CSS
.pointer-none {
  pointer-events: none;
}
```

```javascript:javascript

   //クリック許可
  function addPointerNone() {
    next.classList.add('pointer-none');
    prev.classList.add('pointer-none');
  }

  //クリック禁止 
  function removePointerNone() {
    next.classList.remove('pointer-none');
    prev.classList.remove('pointer-none');
  }
  
  //クリック禁止/解除フロー
  const slidePageTime = 700;//ms

  function pointerNoneFlow() {
    addPointerNone();
    setTimeout(removePointerNone,slidePageTime);
  }
```

### 3-8-3. 学び

:::note
**クリックを禁止する**
pointer-events: none;
:::

### 3-8-4. ポイント

:::note
画面遷移時間とクリック禁止解除時間を合わせることで、
画面遷移中はクリックを無効にする
:::


### 3-8-5. 参考文献
https://techblog.yahoo.co.jp/advent-calendar-2016/css_pointer_events/


<!-- -------------------3-9------------------- -->
## 3-9. HTML読込み完了後の処理
### 3-9-1. やりたいこと
* 「currentIndex」を画像１にしておく
* HTML読込み完了後、
3-6. 自動画面遷移処理を実行する


### 3-9-2. コード
```javascript:javascript
window.addEventListener('DOMContentLoaded',() => {
    currentIndexNext();
    slideNext();
    slideAuto();
  }); 
```

### 3-9-3. 学び

:::note
**HTML読込み完了後にイベント実行**
addEventListener：DOMContentLoaded
:::

### 3-9-4. 参考文献
https://noumenon-th.net/programming/2017/06/11/domcontentloaded/




# 4. まとめ
楽天市場のスライドショーを模写しました。

## 4-1. 得た知識
* [要素のコピー](#3-1-3-学び)
* [アニメーション終了処理](#3-2-3-学び)
* [CSSからクラス要素の適応（追加）](#3-4-3-学び)
* [CSSからクラス要素の適応（削除）](#3-4-3-学び)
* [X秒後に実行する処理](#3-6-3-学び)
* [タイマー処理を終了する](#3-6-3-学び)
* [クリックした後にイベント実行](#3-7-3-学び)
* [クリックを禁止する](#3-8-3-学び)
* [HTML読込み完了後にイベント実行](#3-9-3-学び)


## 4-2. なぜ、JavaScriptだけなのか
* ループするスライドショーの記事を検索してもjQueryを用いた記事がほとんどだった。
* JavaScriptを勉強し始めたてのため、ライブラリを使わずに実装したかった。

## 4-3.やってみて
* 制作時間、約1週間時間のため滅茶苦茶しんどかった。
ただ、Progateやドットインストールでの学びより、数十倍の学びを得ることができた。

* 楽天市場のHPの欠陥気付けたときは面白かった。
あの時、キャプチャしておけば...

## 4-4. 次やってみたいこと
* 非同期処理
* オブジェクト指向



# 5.備考
## 5-1. 記事作成までにやったこと
* Progate：JavaScript：1周
* ドットインストール
    * はじめてのJavaScript：100%
    * 詳解JavaScript 基礎文法編：100%
    * 詳解JavaScript オブジェクト編：77%
    * 詳解JavaScript DOM編：88%
    * JavaScriptでカルーセルを作ろう：100%
    * JavaScriptでストップウォッチを作ろう：50%
* JavaScript[完全]入門：適宜確認

ドットインストールは、必要そうな動画を視聴しただけで、コーディングはしていません。
常に2倍速で、不明な点は何回も何回も視聴しました。

![dotinstall.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1483743/427de521-49ea-52cf-27b5-988cadddc20f.png)

## 5-2. 開発環境
ブラウザ：GoogleChrome(100.0.4896.88)
VScode  ：1.68.1