[Ionic]よく見るタブメニューのページをion-slidesで実装する

この記事は[Ionic Advent Calendar 2017](https://qiita.com/advent-calendar/2017/ionic)の23日目です。

---

スマートニュースやグノシー、TechFeedなどでよく見るタブメニューのナビゲーションがあるページをion-slidesで実装してみます。

[Demo](https://ionic-my-sandbox.firebaseapp.com/#/tab-menu)
[ソースコード](https://github.com/scrpgil/ionic-my-sandbox/tree/master/src/pages/tab-menu)

## どんなものか？

こんな感じのUI（スマートニュースより）です。タブメニューとリストが連動しているUIです。

![smart.gif](https://qiita-image-store.s3.amazonaws.com/0/165335/da443a04-4713-8c4e-c982-19291a12645f.gif)

正式名称わからず。。。

## ソースコード

### **HTMLの記述**

[ソースコード](https://github.com/scrpgil/ionic-my-sandbox/blob/master/src/pages/tab-menu/tab-menu.html)

大きく分けて、タブメニューの部分と、リスト表示の部分があります。

１．タブメニューは、現在表示中の項目の色を変えるためにngClassでactiveクラスを付与するようにしています。

```html
<button [ngClass]="{'active':idx==activeIndex}" ion-button (click)="change(idx)" clear>{{segment.name}}</button>
```

(tap)じゃなくて(click)を使用しているのは(tap)だとAndroidでスクロールができなくなったからです。(tap)と(click)の違いについてもうちょっと勉強しておいた方がよいな。。。


２．ion-slidesにionSlideWillChangeを記述してスライド切り替え時に関数onSlideChangeStartイベントが発火するようにしておきます。

```html
<ion-slides (ionSlideWillChange)="onSlideChangeStart()" class="tabmenu-slides" #Slides>
```

ion-slidesはアウトプットイベントが豊富なので微調整を行いたい時は、他のアウトプットイベントを記述することもできます。

### **CSSの記述**

[ソースコード](https://github.com/scrpgil/ionic-my-sandbox/blob/master/src/pages/tab-menu/tab-menu.scss)

１．ion-contentのスクロールを止めるために.scroll-contentにoverflow-y:hiddenを設定しています。この設定をしておかないとion-listのスクロール操作がたまに失敗する（画面全体が動く）のであったほうがよいです。

２．.tabmenuの横並びはdisplay:flex;を使ってます。記述量少なくてすむし最高ですね。

３．以下は、ion-slides内のリストをスクロールさせるための記述です。

```css
.slide-zoom {
    height: 100%;
    overflow-y: auto;
}
```

### **TypeScriptの記述**

[ソースコード](https://github.com/scrpgil/ionic-my-sandbox/blob/master/src/pages/tab-menu/tab-menu.ts)

１．必要な変数を用意する

```typescript
export class TabMenuPage {
    @ViewChild(Slides) slides: Slides;
    @ViewChild('tabmenu') tabmenu:any;
    segments:any = [];
    activeIndex:number = 0;
    // 以下略
```
ViewChildを使って、slides、tabmenuに参照できるようにしておきます。segmentsはslidesで表示する要素。activeIndexは現在表示中のリストがslideの何番目かを格納しておく変数です。

２．Mockデータを読み込む

```typescript
ionViewDidLoad() {
    this.segments = this.mock.getSegmentItems();
}
```

ionViewDidLoad内でslidesに表示するMockデータを読み込んでいます。Mockは後々他でも使えるようにProviderにしました。

[Providerのソースコード](https://github.com/scrpgil/ionic-my-sandbox/blob/master/src/providers/mock/mock.ts)

３．タブメニューを押した時の処理

```typescript
change(idx){
    this.activeIndex = idx;
    this.slides.slideTo(this.activeIndex, 500);
}
```

タブメニューを押したときには、押した番号をactiveIndex変数に格納するのと、slidesを移動する処理を行っています。

slideTo(移動先, 移動スピード)はion-slidesが提供している関数です。第二引数は移動するアニメーションの間隔でmsが単位です。今回は、500msとしています。

４．スワイプした時の処理

```typescript
onSlideChangeStart(){
    let index = this.slides.getActiveIndex();
    // しきい値チェック。タブメニューをはみ出してたらリターン
    if(this.tabmenu.nativeElement.children.length <= index){
        return;
    }
    this.activeIndex = index;

    // タブメニューの移動
    let start = this.tabmenu.nativeElement.scrollLeft;
    let end = this.tabmenu.nativeElement.children[index].offsetLeft;
    let dir = 1;
    // 進行方向を決定(1：進む、-1：戻る)
    if(start > end){
        dir = dir * -1;
    }
    // 移動量を決める。
    let speed = Math.abs(end - start) / 20;
    let cnt = 0;
    // endの位置に来るまでループ
    let obs = Observable.interval(1).subscribe((x) => {
        cnt = cnt + (speed * dir);
        this.tabmenu.nativeElement.scrollLeft = start + cnt;
        // 進む場合
        if((start+cnt) >= end && dir == 1){
            obs.unsubscribe();
        }
        // 戻る場合
        if((start+cnt) <= end && dir == -1){
            obs.unsubscribe();
        }
    });
}
```

こちらは、ion-slidesのスライドを移動した時の処理です。

４.１．現在のactiveIndex値を取得します。値の取得はion-slides提供のgetActiveIndex()を使用しています。

４.２．しきい値チェックです。これをやらないと一番右のスライドからさらに右にスライドしたときにエラーになります。

４.３．タブメニューの移動アニメーションをするために各種変数を用意しています。

```typescript
// タブメニューの移動
let start = this.tabmenu.nativeElement.scrollLeft;
let end = this.tabmenu.nativeElement.children[index].offsetLeft;
let dir = 1;
// 進行方向を決定(1：進む、-1：戻る)
if(start > end){
    dir = dir * -1;
}
// 移動量を決める。
let speed = Math.abs(end - start) / 20;
let cnt = 0;
```

４.４．用意した変数を使ってスクロール位置が適正の場所にくるまでループします。ループには、interval使ってます。時間計測がやりやすかったので。。。


## まとめ

こうして出来上がったのが以下のUIです。

![swipe.gif](https://qiita-image-store.s3.amazonaws.com/0/165335/74a02c8c-4c3e-354d-305c-eb9da692d5b9.gif)

ion-slidesは豊富な機能が豊富なので、使い方しだいで色々なものに応用できると思います。

ケチを付けるなら上のメニューが動くときに少しラグがあります。
あと、表示するアイテムが増えてくると動作が重くなることが予想されるので、なにかしら対策を打つ必要はありそうです。

でも、簡単に思い描いたとおりに実装ができるのは素直に嬉しいです。ブラウザですぐに開発できて、Web版をサンプルとして配布できるのはうれしいですね。
興味ある人は是非つかってみてください。それでは。。。