kaminari + jscrollを使った無限スクロールの実装

# はじめに
Railsで無限スクロールを実装してみたので、その備忘録的に投稿しようと思います。

kaminariというページネーションを簡単に実装できるgemを使い、無限スクロールを実装してみたいと思います。
無限スクロールにはjscrollというjQueryのライブラリを使います。

# 無限スクロールとは
無限スクロールはメルカリの商品一覧でも見る、下までスクロールすると新しい商品が自動的に読み込まれるあれです。

無限スクロールにはページネーションを先に実装する必要があります。
ページネーションにはkaminariというgemを使います。

Railsのバージョンは5です。

# kaminariのインストール

```ruby:Gemfile
gem 'kaminari'
```
を記載して、

```ruby
bundle install
```
を実行するだけです。

# jscrollのインストール
jscrollには現時点では(2019年2月)gemが存在しないと思われますので、公式のページからダウンロードしてきます。

なお、jscrollを使うにはjQueryが必要です。
[railsでjQueryを使えるようにするにはこちら](https://qiita.com/ngron/items/95846bd630a723e00038)

[jscroll公式ページ](https://jscroll.com/#/)

* CDNで使えるようにする

手軽に試してみたい人はこちら。

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/jscroll/2.4.1/jquery.jscroll.min.js"></script>
```

* ローカルに落とす

こちらのdistからjquery.jscroll.min.jsをダウンロードしてきます。
[jscrollのGithub](https://github.com/pklauzinski/jscroll/tree/master)

そして、asset/javascripts配下にダウンロードしたjquery.jscroll.min.jsを置いて、(読み込めれば場所はどこでも)

```javascript:assets/javascripts/application.js
//= require jquery.jscroll.min.js
```
を記載。

# ページネーションを実装
今回はDBに登録してあったプログラミング言語を一覧で表示するだけの画面を作っていきたいと思います。

skillsというcontrollerを作成し、以下を記載。

```ruby:skills_controller.rb
class SkillsController < ApplicationController
  def index
    @skills = Skill.page(params[:page]).per(10)
  end
end
```

.page(params[:page]).per(10)の部分が一画面に10個の要素を表示するということになります。

そして、index.html.erbに

```erb:index.html.erb
<ul class="skill-list jscroll">
  <% @skills.each do |skill| %>
    <li><%= skill.skill_name %></li>
  <% end %>
  <%= paginate @skills %>
</ul>
```
<%= paginate @skills %>の部分がページの部分。

以上を記載すると、ページネーションが実装できます。とても簡単ですね。
以下のような画面になりました。

![pagination.png](https://qiita-image-store.s3.amazonaws.com/0/290687/0b61cb31-0e9d-2ed1-ed44-efe0ee375c07.png)

# 無限スクロールを実装
無限スクロールは以下のようにして実装できます。
以下のソースは画面を開いた段階で、次の要素が読み込まれる実装になります。

coffeescript

```javascript:skills.coffee
$ ->
  $('.jscroll').jscroll
  return
```

jQuery

```javascript
$(function() {
  $('.jscroll').jscroll;
});
```
ただ、これだけだと動きません・・・。次のページへのリンクを指定する必要があるためです。
このようにすると、画面を読み込むと自動的に次のページが読み込まれます。

coffeescript

```javascript:skills.coffee
$ ->
  $('.jscroll').jscroll
    nextSelector: 'span.next a'
  return
```

jQuery

```javascript
$(function() {
  $('.jscroll').jscroll({
    nextSelector: 'span.next a'
  });
});
```

次のリンクを指定するにはnextSelectorを指定してやります。
ただ、ここれだけでは変な感じで読みこまれると思います。

![pagination.png](https://qiita-image-store.s3.amazonaws.com/0/290687/3fb1f201-7afd-4e7c-24cf-2de0970c99e1.png)

読み込まれている場所はずれているし、jsのconsoleを見ると、rails-ujsが既に読み込まれていますよ、みたいなエラーが出ていると思います。

これは、次のページのhtmlを<strong>全て</strong>読み込んでしまっていて、javascriptなども重複して読み込んでしまっているのでエラーが出ています。

なので、次のページの読み込む要素を指定してやって、その要素だけ読み込ませるようにします。

coffeescript

```javascript:skills.coffee
$ ->
  $('.jscroll').jscroll
    contentSelector: '.skill-list'
    nextSelector: 'span.next:last a'
  return
```

jQuery

```javascript
$(function() {
  $('.jscroll').jscroll({
    contentSelector: '.skill-list',
    nextSelector: 'span.next:last a'
  });
});
```

次にロードする要素として、ulの.skill-listを指定しています。

nextSelectorでspan.next:last aで:lastを指定しているのは、
読み込むリンクはhtmlを読み込んでいくとどんどん増えていきます。

なので、読み込まれた最後のリンクを指定してやることで、どんどん次のページを読み込むようにしています。

ulはpaddingが入ることを忘れていたので、

```scss:skills.scss
ul.skill-list {
  padding: 0;
}
```
を記載。すると、以下のように。
![pagination.png](https://qiita-image-store.s3.amazonaws.com/0/290687/c48518a8-40ef-0b2b-d2a5-e54b36e4dece.png)

あとはページの部分が邪魔なので、

```scss:skills.scss
nav.pagination {
  display: none;
}
```
でページ部分を表示させないようにする。

# 最終的な画面
Loadingしている時、
![pagination.png](https://qiita-image-store.s3.amazonaws.com/0/290687/512a90f7-9c81-57a7-5714-de1a16e78317.png)

Loading後
![pagination.png](https://qiita-image-store.s3.amazonaws.com/0/290687/1389b4bb-b09f-985b-d483-8aaf902f8d9a.png)

後は、画面下までスクロールしたことをjavascriptで検知させて、$('.jscroll').jscrollを呼び出してやれば次の要素を読み込めます。

# 画面下までスクロールしたことを判定
[こちら](https://www.softel.co.jp/blogs/tech/archives/4308)の記事を参考にさせて頂きました。

coffeescript

```javascript
$(window).on 'scroll', ->
  scrollHeight = $(document).height()
  scrollPosition = $(window).height() + $(window).scrollTop()
  if (scrollHeight - scrollPosition) / scrollHeight <= 0.05
    # スクロールの位置が下部5%の範囲に来た場合
    $('.jscroll').jscroll
      contentSelector: '.skill-list'
      nextSelector: 'span.next:last a'
    return
  return
```

jQuery

```javascript
$(window).on('scroll', function() {
	scrollHeight = $(document).height();
	scrollPosition = $(window).height() + $(window).scrollTop();
	if ( (scrollHeight - scrollPosition) / scrollHeight <= 0.05) {
          $('.jscroll').jscroll({
            contentSelector: '.skill-list',
            nextSelector: 'span.next:last a'
          });
	}
});
```

これで、画面下までスクロールしたら自動的に、次の要素が読み込まれるようになります。

# おわりに
ライブラリなどを使うと非常に簡単に無限スクロールが実装できます。

結構モダンなページがお手軽にできると思いますので、是非お試しあれ。