[Rails]kaminariとjscrollを使って無限スクロールを実装

今回はポートフォリオサイト製作時に無限スクロール機能を実装しましたので、備忘録として記事にしておきます。

なお無限スクロールの実装方法は色々あると思いますが、今回はkaminariとjQueryのライブラリのjscrollを使って、実装していきます。

#開発環境

> ruby 2.6.3
> Rails 5.2.6

#前提

- 投稿サイトを想定
- なにかしらの投稿サイトで一覧画面にて機能を実装
- kaminariについてなんとなくでいいので知識がある
- jQueryを使える状態

モデル名、変数名等は適宜、ご自身の開発環境に変換してお考えください。

user  ユーザー
post  投稿

#無限スクロールとは
Rails初学者がページネーションで真っ先に学習するのがkaminariかなと思います。
ただ、画面の下に1.2.3...の表示が出て、という表示方法はよくある投稿サイトなどを作っている際にはあまり実用的とは思えません。

そこで、TwitterやYouTubeのようにページ遷移すること無く、下にスクロールするだけでどんどんコンテンツが出てくるような状態、無限スクロールをkaminariを応用して実装します。

#kaminariのインストール

まずは`gem 'kaminari'`をインストールします。

```erb:Gemfile
gem 'kaminari'
```

を追加して

```
$bundle install
```

これで、kaminariをインストールできました。

#jscrollの準備

jscrollを使えるようにするには、GitHubからダウンロードしてきます。
[jscrollのGitHub](https://github.com/pklauzinski/jscroll/tree/master)

`Code`ボタンを押すと`Download ZIP`が一番下にあるので、そこからZIPファイルをダウンロードします。
解答したZIPファイルのdistフォルダの`jquery.jscroll.min.js `ファイルをassets/javascript直下にドラッグアンドドロップします。

そしてjscrollを読み込ませます。
記述の順番でエラーになるケースもあるみたいなので、ぼくの環境ではこの順番でエラーが起きていないので参考にのせておきます。

```assets/javascripts/application.js
:
//= require jquery3
//= 以下1行を追加
//= require jquery.jscroll.min.js
//= require popper
//= require bootstrap-sprockets

//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree .
:
```

#ページネーションを実装

kaminariを使ってページネーションの実装をしていきます。

まずはコントローラーで１ページの表示件数を指定。

```controllers/posts_controller.rb
 def index
    @posts = Post.page(params[:page]).per(3)
  end
```

次にビューでページネーションの表示。

```erb:views/posts/index.html.erb
<% @posts.each do |post| %>
<div>
  <%= link_to post_path(post.id) do %>
    <%= attachment_image_tag post, :image, :fill, 200, 200 %>
  <% end %>
  <p>説明：<%= post.content %></p>
  <p>ユーザー名：<%= post.user.name %></p>
  <p><%= link_to "#{post.comments.count} コメント", post_path(post.id) %></p>
</div>
<% end %>
<!--ページ表示部分-->
<%= paginate @posts %>
```

ここまでで、kaminariを使ったページネーション機能が実装できているので、あとはjscrollを使って無限スクロールにしていきます。

#無限スクロール実装

####無限スクロールの範囲を指定
まずはビューの一覧表示される部分をdivタグで囲います。
この囲った範囲をスクロールした際に表示していきます。

```erb:views/posts/index.html.erb
<!--class名をつけてdivタグで囲う-->
<div class="scroll-list jscroll">
  <% @posts.each do |post| %>
  <div>
    <%= link_to post_path(post.id) do %>
      <%= attachment_image_tag post, :image, :fill, 200, 200 %>
    <% end %>
    <p>説明：<%= post.content %></p>
    <p>ユーザー名：<%= post.user.name %></p>
    <p><%= link_to "#{post.comments.count} コメント", post_path(post.id) %></p>
  </div>
  <% end %>
  <%= paginate @posts %>
</div>
```

####無限スクロールの処理
さきほど追加したdivタグのクラス名を対象に処理を記述していきます。

```assets/javascripts/application.js
// 無限スクロールの処理
$(window).on('scroll', function() {
    scrollHeight = $(document).height();
    scrollPosition = $(window).height() + $(window).scrollTop();
    if ( (scrollHeight - scrollPosition) / scrollHeight <= 0.05) {
          $('.jscroll').jscroll({
            contentSelector: '.scroll-list',
            nextSelector: 'span.next:last a'
          });
    }
});
```

これが完成形です。
この処理では、大きく分けて２つの処理を記述しています。

- 無限スクロールを発動させる条件の処理
- 無限スクロールの処理

この２つを別々に解説していきます。

####無限スクロールを発動させる条件の処理
ここではどこまで画面をスクロールしたら無限スクロールが発動するのかという条件を決めています。

```js:発動条件指定
// 画面をスクロールしたときという状況を指定
$(window).on('scroll', function() {
    scrollHeight = $(document).height();
    scrollPosition = $(window).height() + $(window).scrollTop();
    // スクロールの位置が下部5%の範囲に来た場合
    if ( (scrollHeight - scrollPosition) / scrollHeight <= 0.05) {
    // まとまると画面をスクロールしてスクロールの位置が下部5%の範囲に来たときに
    // 以下の処理を実行
    :
```

####無限スクロールの処理
発動条件が決まったので、無限スクロールの処理を解説していきます。

```js:無限スクロール
$('.jscroll').jscroll({
  // 読み込んだ要素を追加する場所
  contentSelector: '.scroll-list',
  // 次のページのリンクの場所
  nextSelector: 'span.next:last a'
});
```

これだけだとわかりにくい。。

流れで説明すると、

①スクロールの位置が下部5%の範囲に来る
②`nextSelector: 'span.next:last a'`でビューのページ表示部分が読み込まれて、次のページの要素を取得
③取得した次のページの要素を`'.scroll-list'`の中に入れる

という流れです。

次のページのリンクの場所で`<%= paginate @posts %>`ではなく、`'span.next:last a'`を指定しているのは、読み込むリンクはhtmlを読み込んでいくとどんどん増えていきます。

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

#ページ表示部分を非表示に

これで、無限スクロールの実装は出来たのですが、ページ表示部分が邪魔なので、非表示にしておきます。

```assets/stylesheets/application.scss
// ページ表示部分を非表示
 nav.pagination {
  display: none;
}
```

#さいごに

kaminariとjscrollを使ってかんたんに無限スクロールの実装をしてみました。

機能実装にあたり、下記記事を参考にさせていただきました。

[kaminari + jscrollを使った無限スクロールの実装](https://qiita.com/s_yasunaga/items/ab17424912ee90f853d7)