- Ruby / Rails関連
Railsの技: Turbo Frameとskeleton loaderでHotwireのコンテンツをlazy loadingする(翻訳)
Railsの技: Turbo Frameとskeleton loaderでHotwireのコンテンツをlazy loadingする(翻訳)
HotwireはBasecampが提供する新しいフロントエンドツール集で、最小限のJavaScriptを書くだけで「リアクティブな」Railsアプリを構築できます。
Hotwireの機能の中で、サーバーサイドでレンダリングしたHTMLをリアルタイムでストリーミングできる機能が最大の目玉だと考える人もいますが、私の推しはTurbo Frameが追加されたことです。
Turbo Frameはiframe
の超高性能版で、使ってみると実に感心します。Frameはページの一部を表現し、そこに独自のナビゲーションコンテキストを内包しています。
中でもひときわ強力なのは、Turbo Frameのlazy-loading(遅延読み込み)機能です。このパターンの例としては、普段よく目にするGitHubのactivityフィードがあります。
最初にページの「外殻」を読み込み、続いてAJAX呼び出しを実行すると、コンテンツの残りをフェッチしてページを埋められます。これは遅いページの高速化に絶大な効果を発揮します。
しかし、この機能を使うとページコンテンツががたついてしまうという欠点があります。「読み込み中」のスピナー表示は小さい四角形ですが、表示されると多数のイベントがフィードされて四角形が下に展開されます。
これを解決する方法のひとつは、"skeleton screen"か"skeleton loader"を使うことです。このUIパターンでは、空白のコンテンツをプレースホルダとして用いることで、コンテンツが最終的に読み込まれたときのがたつきを軽減します。
この2つの概念は、ピーナッツバターとゼリーのように相性抜群です。
使い方
Turbo Frameの基本的なlazy-loadingは以下のようになります。
<turbo-frame id="feed" src="/feed">
/feedが読み込まれるとこのコンテンツが置き換えられる
</turbo-frame>
ここにsrc
属性を指定すると、Frameはページ読み込み時に自動的にAJAXリクエストを発行し、レスポンス内で<turbo-frame>
にマッチする部分をそのコンテンツで置き換えます。
さらに、loading
プロパティに"eager"
または"lazy"
も設定できます。"eager"
は直ちに読み込みを開始し、"lazy"
はフレームがページに出現したときに読み込みを開始します。
GitHub ActivityのフィードをRailsで行うと、以下のような感じになるでしょう。
<!-- app/views/home.html.erb -->
<div>(他のコンテンツ)</div>
<%= turbo_frame_tag :feed, src: activity_feed_path, loading: :lazy do %>
読み込み中...
<% end %>
この基本的な「読み込み中...」メッセージを独自のskeleton loaderに置き換えることでページをレベルアップできます。Tailwind CSSに組み込まれているanimate-pulse
クラスのおかげで実に簡単です。
適当な灰色の四角形をフレームの初期コンテンツとして追加します。
<!-- app/views/home.html.erb -->
<div>(他のコンテンツ)</div>
<%= turbo_frame_tag :feed, src: activity_feed_path, loading: :lazy do %>
<div class="flex flex-col space-y-6">
<% 10.times do %>
<div class="animate-pulse flex space-x-4">
<!-- アバター -->
<div class="rounded-full bg-gray-400 h-12 w-12"></div>
<!-- 詳細 -->
<div class="flex-1 space-y-4 py-1">
<div class="h-4 bg-gray-400 rounded w-3/4"></div>
<div class="space-y-2">
<div class="h-4 bg-gray-400 rounded"></div>
<div class="h-4 bg-gray-400 rounded w-5/6"></div>
</div>
</div>
</div>
<% end %>
</div>
<% end %>
仕上げに、activity_feed_path
アクションが返すコンテンツをTurbo Frameにマッチするようラップしておきます。これによりフレームのコンテンツが自動的に差し替えられ、読み込み中のステートが更新されます。
class ActivityFeedController < ApplicationControler
def show
@events = Current.user.activity.last(20)
end
end
注意: このレスポンスではFrameにsrc
属性やloading
属性を設定しないでください(設定すると無限ループになってしまいます)。
<!-- app/views/activity_feed/show.html.erb -->
<%= turbo_frame_tag :feed do %>
<%= render partial: "feed_item", collection: @events %>
<% end %>
Turbo Frameの真の力を理解できれば、昔ながらのHTMLページをlazy-loadingできるようになり、アプリのあらゆる場所で役に立つでしょう。
参考情報
- Hotwireドキュメント: Turbo Frames
-
Tailwindドキュメント: animate-pulse
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。