okuno

スマートニュースやグノシーのような、スワイプタブ切替機能を実装する

グノシーやスマートニュース、日経電子版などのニュース系アプリでよく使われているUIで、スワイプで記事一覧が横スライドしていくものがあります。
大量の更新記事をカテゴリ毎に効率よく見せるのに非常に効果的なUIです。
webサイトではあまり使われている場面は多くないですが、ヤフーのニュース部分ではこのUIが採用されています。
今回はこのUIを、jQueryプラグインのbxSliderをうまく活用しながら実装してみようと思います。

完成系はこちらになります。GitHub上でコードも公開しておりますのでよければ使ってください。

※iOS8.13以上 safari対応
※Andoroid4.2.2以上 chrome・Android標準ブラウザ対応

DEMO
ソース

ポイントはタブの横スクロールの調整です。
タブが1画面におさまっていないため、コンテンツのスライド部分に合わせて表示位置を微妙に調整していかないと、現在地のタブが見えなくなるという状態がでてきます。
その他はbxSliderのイベントやメソッドをうまく利用すれば難しくはありません。

今回のファイル構成

index.html
┣ assets
┃ ┣ css
┃   ┗ reset.css
┃   ┗ common.css
┃ ┣ js
┃   ┗ jquery-2.1.4.min.js
┃   ┗ jquery.bxslider.js
┃   ┗ common.js

HTMLのソース

まずはHTMLです。特別なことは何もありません。
jQueryのスライドプラグインbxSliderを読み込んでいます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="assets/css/reset.css" />
<link rel="stylesheet" type="text/css" href="assets/css/common.css" />
<title></title>
</head>
 
<body>
<div class="container">
    <div class="tabContainer">
        <div class="tab">
            <div class="tab__button active"><a href="#">トップ</a></div>
            <div class="tab__button"><a href="#">エンタメ</a></div>
            <div class="tab__button"><a href="#">スポーツ</a></div>
            <div class="tab__button"><a href="#">グルメ</a></div>
            <div class="tab__button"><a href="#">コラム</a></div>
            <div class="tab__button"><a href="#">国内</a></div>
        </div>
    </div>
    <div class="contents">
 
        <div class="contents__content">
            <div><a href="#"><span>トップコンテンツトップコンテンツトップコンテンツトップコンテンツトップコンテンツ</span></a></div>
            <div><a href="#"><span>トップコンテンツトップコンテンツトップコンテンツトップコンテンツトップコンテンツ</span></a></div>
            <div><a href="#"><span>トップコンテンツトップコンテンツトップコンテンツトップコンテンツトップコンテンツ</span></a></div>
            <div><a href="#"><span>トップコンテンツトップコンテンツトップコンテンツトップコンテンツトップコンテンツ</span></a></div>
            <div><a href="#"><span>トップコンテンツトップコンテンツトップコンテンツトップコンテンツトップコンテンツ</span></a></div>
            <div><a href="#"><span>トップコンテンツトップコンテンツトップコンテンツトップコンテンツトップコンテンツ</span></a></div>
        </div>
 
        <div class="contents__content">
            <div><a href="#"><span>エンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツ</span></a></div>
            <div><a href="#"><span>エンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツ</span></a></div>
            <div><a href="#"><span>エンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツ</span></a></div>
            <div><a href="#"><span>エンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツ</span></a></div>
            <div><a href="#"><span>エンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツ</span></a></div>
            <div><a href="#"><span>エンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツエンタメコンテンツ</span></a></div>
        </div>
 
        <div class="contents__content">
            <div><a href="#"><span>スポーツコンテンツスポーツコンテンツスポーツコンテンツスポーツコンテンツ</span></a></div>
            <div><a href="#"><span>スポーツコンテンツスポーツコンテンツスポーツコンテンツスポーツコンテンツ</span></a></div>
            <div><a href="#"><span>スポーツコンテンツスポーツコンテンツスポーツコンテンツスポーツコンテンツ</span></a></div>
            <div><a href="#"><span>スポーツコンテンツスポーツコンテンツスポーツコンテンツスポーツコンテンツ</span></a></div>
            <div><a href="#"><span>スポーツコンテンツスポーツコンテンツスポーツコンテンツスポーツコンテンツ</span></a></div>
            <div><a href="#"><span>スポーツコンテンツスポーツコンテンツスポーツコンテンツスポーツコンテンツ</span></a></div>
        </div>
 
        <div class="contents__content">
            <div><a href="#"><span>グルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツ</span></a></div>
            <div><a href="#"><span>グルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツ</span></a></div>
            <div><a href="#"><span>グルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツ</span></a></div>
            <div><a href="#"><span>グルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツ</span></a></div>
            <div><a href="#"><span>グルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツ</span></a></div>
            <div><a href="#"><span>グルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツグルメコンテンツ</span></a></div>
        </div>
 
        <div class="contents__content">
            <div><a href="#"><span>コラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツ</span></a></div>
            <div><a href="#"><span>コラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツ</span></a></div>
            <div><a href="#"><span>コラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツ</span></a></div>
            <div><a href="#"><span>コラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツ</span></a></div>
            <div><a href="#"><span>コラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツ</span></a></div>
            <div><a href="#"><span>コラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツコラムコンテンツ</span></a></div>
        </div>
 
        <div class="contents__content">
            <div><a href="#"><span>国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ</span></a></div>
            <div><a href="#"><span>国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ</span></a></div>
            <div><a href="#"><span>国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ</span></a></div>
            <div><a href="#"><span>国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ</span></a></div>
            <div><a href="#"><span>国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ</span></a></div>
            <div><a href="#"><span>国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ国内コンテンツ</span></a></div>
        </div>
 
    </div>
</div>
<script>window.jQuery || document.write('\x3cscript src="/assets/js/jquery-2.1.4.min.js"\x3e\x3c/script\x3e')</script>
<script src="assets/js/jquery.bxslider.js"></script>
<script src="assets/js/common.js"></script>
</body>
</html>

CSS

cssです。特筆すべきは12〜28行目です。この部分でタブのはみ出た部分をスクロールできるようにしています。
その他は特別なことはしておりません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
body{
    font-family:-apple-system,’Lucida Grande’,‘Helvetica Neue’,’Hiragino Kaku Gothic ProN’,‘游ゴシック’,’メイリオ’,meiryo,sans-serif;
 
    -webkit-text-size-adjust: 100%;
}
 
.container{
    max-width: 400px;
    margin: auto;
}
 
.tabContainer {
    overflow-x: auto;
}
.tabContainer::-webkit-scrollbar {
    height: 5px;
}
.tabContainer::-webkit-scrollbar-track {
    background: #000;
}
.tabContainer::-webkit-scrollbar-thumb {
    background: #000;
}
 
.tab{
    display: table;
    margin-top: 20px;
}
 
.tab__button{
    display: table-cell;
    text-align: center;
    background-color: #000;
    vertical-align: middle;
    border: 2px solid white;
    border-bottom-width: 4px;
    min-width: 80px;
}
 
.tab__button.active{
    border-bottom: none;
}
 
.tab__button a{
    padding: 10px;
    color: #fff;
    display: block;
    text-decoration: none;
    font-size: 12px;
}
 
.contents__content{
    background-color: #ccc;
    text-align: center;
}
 
.contents__content div{
    clear: left;
}
 
.contents__content div a{
    display: table;
    width: 100%;
    text-decoration: none;
    padding: 10px;
    border-bottom:1px solid #000;
    color:#222;
    text-align: left;
    line-height: 1.5em;
    font-size: 14px;
}
 
.contents__content div a span{
    display: table-cell;
    padding-left: 10px;
    padding-right: 10px;
}
 
.contents__content div a:before{
    content:'';
    width: 50px;
    height: 50px;
    display: block;
    background-color: #fff;
    display: table-cell;
}

JavaScript

今回の一番重要な部分はJavaScriptです。下記がcommon.jsの内容です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
$(document).ready(function(){
 
    /******************************************
    事前準備
    *******************************************/
 
    //タブボタンの数を取得
    var tabQuantity = $('.tab__button').length;
 
    //タブの長さとボディの長さの差分を取得
    var tabExtraDistance = $('.tab').width() - $('.tabContainer').width();
 
    /******************************************
    スライダー発動
    *******************************************/
 
    var slider = $('.contents').bxSlider({
        pager:false,
        controls:false,
        onSlideBefore: function($slideElement, oldIndex, newIndex){
            //スライドする時に関数を呼び出す。newIndexはスライダーの現在地。
            slideChange(newIndex);
        }
    });
 
    /******************************************
    スライドする時に発動する関数。タブの表示調整を行う。
    *******************************************/
 
    function slideChange(newIndex){
 
        //クラスを調整
        $('.tab__button').removeClass('active');
        $('.tab > div:nth-child(' + ( newIndex + 1 ) + ')').addClass('active');
 
        //スクロールするべき距離を取得。タブ全体の長さ / ( タブの個数 - 1 ) * スライドの現在地
        var scrollDestination = ( tabExtraDistance / (tabQuantity - 1) ) * ( newIndex );
 
        //スクロール位置を調整
        $('.tabContainer').animate({ scrollLeft: scrollDestination }, 'slow');
 
    }
 
    /******************************************
    タブボタンクリックで発動する関数
    *******************************************/
 
    $('.tab__button').on('click',function(e){
 
        //何番目の要素かを取ってスライドを移動する
        var nth = $('.tab__button').index(this);
        slider.goToSlide(nth);
 
        //クリックイベントを無効化
        e.preventDefault();
 
    })
 
});

基本的にbxSliderの機能をベースに使用しています。
記事一覧部分をbxSliderでスライドさせて、スライドの現在地に合わせてタブ部分のクラス名をつけかえて、選択タブの表示を調整しています。
また、タブ部分の横スクロール位置を、コンテンツの現在スライドに合わせて細かく調整しています。

まとめ

いかがでしたか?
ネィティブアプリのUIはブラウザでは難しいものもありますが、非常に効果的なものも多くあるので、諦めずに挑戦することが大切です!

新しいウェブ体験を作ろう TAMのPWA開発
お問い合わせはこちら