Railsフロントエンド: ViewComponent+Tailwind CSS+Hotwireの便利技8つ(翻訳)
フロントエンドのコードは、歴史的に少々軽く見られていました。「HTMLは本物の言語じゃない!」「CSSはサイテー!」「JavaScriptもそうだ!」。嘆かわしい話です。Railsは1個人だけの開発チームに、すべてが完全に揃った製品を構築できる本物のスーパーパワーを与えてくれるのですから。
Railsアプリ作成がさらに楽しくなるツールは、Rails自身にもサードパーティにもあります。Hotwire、Tailwind CSS、そしてViewComponentなどがそうです。
私はこれまで、こうしたフロントエンドツールをRailsアプリで常用するための、さまざまな便利技やコツ(おそらくベストプラクティス)を収集してきました。今後(まだ書いていなかった)新しい便利技を見つけたら、本記事を更新する可能性があります。
🔗 1: CSSセレクタのリストをビルドするにはclass_names()
を使おう
タグヘルパーのclass_names()
メソッドを使うと以下の作業を素直に行えるので、私はいつも頼りにしています。
- CSSセレクタを条件に応じて追加/削除する
- Tailwind CSSのユーティリティクラスの(長い)リストをメンテ可能に保つ
APIドキュメントを見るとわかるように、class_names()
はtoken_list()
の単なるエイリアスです。
それではコード例を見てみましょう(私のRails Designer DropdownComponentから引用しました)。
class_names(
"absolute text-sm shadow-xl overflow-hidden rounded-lg z-10",
content_min_max_width,
@padding,
{
"bg-white/80 ring-1 ring-inset ring-gray-100 backdrop-blur-md": light_theme?,
"bg-gray-800": dark_theme?
}
)
- 最初の行に書かれているTailwind CSSの「静的な」ユーティリティクラス((
absolute...z-10
))は、常に適用されます。 - 次に、
content_min_max_width
というメソッドを用いていくつかのセレクタを設定しています。ここで何らかの追加作業が行われていることが想像できますね。 - 次に、インスタンス変数
@padding
に入っている何かを追加しています。これはおそらく、コンポーネントに追加される属性でしょう。 - 次の波かっこ
{}
内には、light_theme?
またはdark_theme?
がtrueを返すかどうかに応じてセレクタが設定されます。
これで、デフォルトのセレクタリストと一緒にビルドされたセレクタのリストは、DropdownComponent
では以下のようになります。
"absolute text-sm shadow-xl overflow-hidden rounded-lg z-10 min-w-[8rem] max-w-[16rem] p-4 bg-white/80 ring-1 ring-inset ring-gray-100 backdrop-blur-md"
このclass_names()
メソッドはtag
メソッドにも「組み込まれています」。つまり、以下のようにtag
で使うことも可能なのです。
tag("div", class: { "block": Current.user.admin?, "hidden": !Current.user.admin? })
# => <div class="highlight" />
🔗 2: link_to
(<a>
タグ)にデフォルトスタイルを与える
CSSは長年にわたってずっと強力になり続けています。クラス名(.class
)やid(#unique_id
)だけでHTML要素を選択する時代は終わりました。今ならHTML要素を選択するのにその他の属性も利用できます。たとえば、以下のCSSは私がすべてのRailsアプリで使っているもので、Tailwindの@apply
を使っています。
@layer base {
/* ... */
a:not([class]) {
@apply underline;
&:hover {
@apply no-underline;
}
}
/* ... */
}
これによって、以下のようにクラス属性が存在しないlink_to
にはデフォルトで下線が表示され、マウスオーバーすると下線が消えるようになります。
link_to "Rails Designer`, "https://railsdesigner.com/"
ただし、以下のようにクラス属性があってもclass: ""
のように中身が空の場合は、デフォルトのスタイルは適用されません。
link_to "Rails Designer`, "https://railsdesigner.com/", class: ""
もちろん、リンクのスタイルは自由に微調整できます。
🔗 3: CSS内でdata-*
属性を用いて表示/非表示を切り替える
フロントエンド経験者なら、CSSクラス名にjs-
をプレフィックスすることで「このクラスの"どこか"で"何らかの形で"JavaScriptが使われている」ことを示すというハックを見かけたことがあるでしょう。
上の便利技と同様に、data-*
属性を用いて要素をカスケードダウンすることも可能です。これも私のRails DesignerにあるNavbarComponentから例を引いてみましょう。
<nav class="group/navigation">
<ul class="hidden group-data-[show-menu]/navigation:block">
</ul>
</nav>
上の書き方のコツは、Tailwind CSSが提供しているgroup-*
修飾子を活用していることです。
これにより、ul
要素はデフォルトでは(hidden
属性によって)非表示になりますが、その親であるnav
要素にdata-show-menu
属性が設定されると、ul
要素が表示されます。
実際にこうした属性を切り替えるには、極めてシンプルで再利用可能なStimulusコントローラが必要になることは想像がつくでしょう。
🔗 4: Turbo Frameの内側(や外側)のコンポーネントを別のスタイルにする
Turbo Frameは、モーダル(ダイアログ)を表示するのに優れていると思います。リンクをクリックすると、そのページのturbo_frame
の内側がレンダリングされます。
しかし、単独でも表示可能でなければならない外部ページの場合は、スタイルの一部(ドロップシャドウや絶対位置・固定位置など)を削除したいことがあるでしょう。
ありがたいことに、これはTailwind CSSなら簡単にセットアップできます。アプリケーションのtailwindcss.config.js
ファイルのplugins
配列に以下を追加します。
plugins: [
// ...
function ({ addVariant }) {
addVariant("turbo-frame", "turbo-frame[src] &")
}
]
これで、turbo-frame
というカスタム修飾子が使えるようになりました。他のTailwind CSS修飾子とまったく同じように、md:
やlg:
などのブレークポイントを対象に設定できます。
たとえば以下のように書くと、turbo-frame
の内側がレンダリングされる場合にのみドロップシャドウが表示されるモーダルコンポーネントを作成できます。
<div class="relative turbo-frame:shadow-xl"></div>
turbo-frame[src] &
セレクタは、1つ前の便利技と同じように機能します。
🔗 5: Stimulusコントローラの関数をprivateにする
Rubyではprivateメソッド(やprotectedメソッド)が利用できます。private
という(カーネル)メソッドより下にメソッドを配置すると、privateメソッドになります。
Rubyのクラスではpublicメソッドをなるべく1個にとどめることが推奨されますが、それと同じ理由で、StimulusやJavaScriptのクラスのpublicメソッドをなるべく少なく抑えることが推奨されます。
JavaScriptでprivate関数を作成するには、以下のように関数名の冒頭に#
をプレフィックスします。
class Class {
#thisIsPrivate() {
//...
}
}
私が書く典型的なStimulusコントローラは以下のような感じになります。
export default class extends Controller {
initialize() {
}
connect() {
}
disconnect() {
}
// ここにアクションを定義する
// private
#firstPrivateFunction() {
}
#secondPrivateFunction() {
}
}
デフォルトのconnect
とdisconnect
はクラスの上の方に配置しています。それに続いて、Stimulusコントローラ内で利用可能なアクションを配置します。
そして// private
コメント(これ自体は機能上の意味はありません)に続けてすべてのprivate関数を配置します。
// private
コメントは、単にコードを読みやすくするためのものです。こうするとRubyのクラスとスタイルが似てくるので、私はこのスタイルを使うようにしています。
これは、コントローラを整頓して理解しやすくするのに役に立っています。
🔗 6: turbo_frame
リクエストが誤動作しないようにする
モーダルにかなり依存するUIを構築したことがある人なら、モーダルのスタイル設定がどれほど厄介なことをご存知でしょう。リンクをクリックしてモーダルを開き、あれこれ微調整してリフレッシュしてからリンクを再度クリックする、といった面倒な作業がつきまといます。
ありがたいことに、Railsを使っていれば、モーダルを(automations/new
のようなURLでアクセス可能な)単独のページとして構築しておいて、そこで必要に応じてモーダルのスタイルを微調整するという方法が使えます。ただしこの方法には、ユーザーがそのモーダルを単独のページとして開いてしまう可能性があるという潜在的な問題があります。そうやって開いたモーダルは、おそらく動かないでしょう。
そういうわけで、私はあらゆるRailsアプリのコントローラに以下のconcernを配置しています。
# app/controllers/concerns/frameable.rb
module Frameable
extend ActiveSupport::Concern
private
def ensure_turbo_frame_response
redirect_to root_path unless turbo_frame_request?
end
def production_environment?
Rails.env.production?
end
end
次に、turbo-frame
内でのみ表示されるすべてのアクションに対して、以下のような形で利用します。
before_action :ensure_turbo_frame_response, only: %w[new], if: :production_environment?
こうすることで、production環境の場合にのみ、そのモーダルページはturbo-frame
の内側だけで表示されるようになります。
🔗 7: 色付き背景には不透明度(opacity)も指定しておこう
このささやかな便利技は、Webデザイナーから指示される前に気を利かせて仕込んでおけば、間違いなく喜んでもらえます!
ポイントは、Tailwind CSSの色指定をbg-green-100
のような方法ではなく、bg-green-200/50
のようにopacityも指定しておくことです。これは、特に親要素の背景が(マウスオーバーで)灰色になった場合に効果的です。
以下の画像の左右を見比べてみてください。
専門的な訓練を受けていないと見分けが難しいかもしれませんが、左下の緑のactive
バッジ(透明度指定なし)は、右下の緑のactive
バッジ(透明度指定あり)に比べて、わずかに濁って見えます。
🔗 8: マウスオーバーの遷移に遅延を設定する
UXのちょっとした小技ですが、嬉しさがこみ上げてくること請け合いです。
たとえばカードのような要素にtransition
を追加する場合を考えてみましょう。このときにdelay-75
などのように短い遅延を追加しておくと、ユーザーがマウスをカードにかざしたときに、指定していない遷移がすぐトリガーされなくなり、UXに落ち着きをもたらしてくれます。
例(Tailwind CSSクラスを利用):
...
<li class="flex px-4 py-2 bg-white transition ease-in-out duration-200 delay-75 hover:bg-gray-50"></li>
...
これで、複数表示されているli
要素の上でマウスを素早く動かしても、背景はすぐには変化しなくなり、75ミリ秒経過した場合にのみ変化します。こうすることで、ユーザーが不要な遷移に気を散らされずに済むようになります。
私がすべてのRailsアプリで愛用している便利技やアイデアの紹介は以上です。気に入った技や取り上げて欲しい技がありましたら、ぜひお知らせください!
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。