📜

TurboStreamで作る超簡単な無限スクロール

2024/07/09に公開

弊社ではインタラクティブではない画面を対象に、
RailsとVueを混在したMPAから純粋なRailsのコードへの移行を検証しています。
試しに1画面の置き換えを行う過程で簡単に無限スクロールのUIを実装できたので、共有します。

TurboStreamで作る無限するスクロール

技術仕様等

Rails 7.0以上で動作します。またページネーションにはKaminariを利用しています。

ブラウザバック時にページコンテンツとページ位置は保持されます。
難しいことがやりたい人は下記記事を参考にして、難しい無限スクロールを実装してみてください。

https://blog.ojisan.io/i-hate-infinite-scroll/

Controller

今回はTurboStreamを使ってページネーションを実装するので、下記のように実装します。
format.turbo_streamがある以外は普通のRailsのControllerですね。

class UsersController < ApplicationController
  before_action :set_user, only: %i[ show edit update destroy ]

  def index
    params[:page] ||= 1
    params[:limit] ||= 30

    @users = User.page(params[:page]).per(params[:limit])

    respond_to do |format|
      format.html
      format.turbo_stream
    end
  end
end

View

ここで、users/index.html.erbを実装します。

<p style="color: green"><%= notice %></p>

<h1>Users</h1>

<div id="users">
  <% @users.each do |user| %>
    <%= render user %>
    <p>
      <%= link_to "Show this user", user %>
    </p>
  <% end %>
</div>

<% if @users.next_page %>
  <%= turbo_frame_tag "load_more", src: users_path(page: @users.next_page, format: :turbo_stream), loading: :lazy %>
<% end %>

<%= link_to "New user", new_user_path %>

users/_user.html.erbはこんな感じ。

<div id="<%= dom_id user %>">
  <p>
    <strong>Name:</strong>
    <%= user.name %>
  </p>

</div>

上記で、turbo_frame_tag で次のページのFrameをturbo_frame_tagの"load_more"が画面に表示されるタイミングで src にあるURLの内容を読み込みます。

users/index.turbo_stream.erb

<%= turbo_stream.append "users" do %>
  <% @users.each do |user| %>
    <%= render user %>
    <p>
      <%= link_to "Show this user", user %>
    </p>
  <% end %>
<% end %>

<%= turbo_stream.replace "load_more" do %>
  <% if @users.next_page %>
    <%= turbo_frame_tag "load_more", src: users_path(page: @users.next_page, format: :turbo_stream), loading: :lazy %>
  <% end %>
<% end %>

上記のようにHTML IDの'users'にturbo_streamを経由してロードしたHTMLの内容をAppendしています。またページネーションについては、turbo_streamでreplaceを行うことで、次に読み込むべきTurboFrameに更新しています。

TurboFrameが入れ子にならない

ざっと調べた感じ、TurboFrameが入れ子になってしまうパターンだったり、
TurboFrameが入れ子にならない方法でも、それなりに複雑な処理が必要な方法がメインでした。
こちらの方法ではHTML要素の構造自体も実装もシンプルにまとまりました。

とはいえTurboStreamの多様は危険

複雑なTurboStreamレスポンスは、コードの理解を難しくするので、
一つのTurboStreamのViewの中で多くても許されるのはせいぜい3つの要素の操作くらいだと思います。なので、これ以上TurboStreamでの操作が増えそうということであれば、JSでの実装を検討していいと思っています。

DHHはどう実装しているか?

最後に答え合わせとしてDHHが実際にPaginationで使用していると思われるgistを紹介します。

https://gist.github.com/dhh/f459dfc3455d2376ce3a7ecb026e6fdf

ここではTurboStreamを使わないで、単純にJSで実装されてるようでした。
サーバーサイドのTurboStreamよりも、フロントエンドでのコントローラーのほうが再利用しやすいと考えられます。
コードはJSで実装するほうが長いですが、個別にTurboStreamの処理をサーバーサイドを書いたりする必要はなくなると思うので、好きな方を選択するようにしましょう。

で、どうしたの?

上記DHHの実装をベースにWebComponentsで再実装したものを利用することにしたのでした。

まとめ

このように簡単な無限スクロールもTurboでサクッとできてしまいました。
無限スクロールでも簡単な要件の場合もあると思うのでぜひ試してみてください。

OSIRO テックブログ

Discussion