概要
PF用にアプリを個人開発しているRailsしか知らない素人です。新しいものがいいかなと思ったのでrails7を使ってます。hotwire完全に理解した… までも行き着いてないです😂
書き味とか使ってみてどうとかは他の言語やframeworkを知らないので比較できないのですが、rails6までのwebpackerとかよりは複雑さが緩和されてる気がします(個人の主観です)
環境
# 抜粋です
ruby '3.1.3'
gem 'rails', '~> 7.0.3'
gem 'sprockets-rails'
gem 'pg'
gem 'puma'
gem 'importmap-rails'
gem 'turbo-rails', '~>1.3.2'
gem 'stimulus-rails', '~>1.2.1'
gem 'tailwindcss-rails'
gem 'jbuilder'
gem 'cloudinary'
js周りはimportmap、css-frameworkはtailwindを使ってます。
この構成は作り始める時に、DHHがおすすめしてたからです。DHH愛してる。
画像ファイルの保存先にはcloudinaryを利用してます。upload速度は遅いです。
速度を求めたら国内リージョンのs3とかの方が絶対早いでしょう。知リませんがノーズドリリング。
でもcloudinary君は無料のstarterプランで10GBまで保存させてくれる個人開発に優しいやつ(2022年12月現在)なので、ちょっと遠い事なんか全然大した問題ではないです。
デプロイ先はheroku(こちらは最近無料枠無くなりました… けど離れられない heroku以外考えられない)を利用しているのですがcloudinary殿はherokuのaddonがあり、組み合わせて利用するための公式ドキュメントがあることも有難いです。英文ですが… 日本語ページ充実してどうぞ。
ファイルサイズにバリデーション掛けて、active_storageでdirect_upload機能(超簡単に設定できます)を利用することで、個人的には使い物にならない感じではないと自己暗示を掛けてます。
cloudinary様は自前で画像変換機能があるので、clientやserverに画像処理という負荷をかけなくていいというすごい利点があります。以上布教活動でした。
やりたかったこと
タイトルの通り、stimulusで写真アイコンにクリック、タップで写真のモーダルが開くようにしたい。
結論
いろんなサイトを覗いて、最も参考にしたのは下記の2つ
a. https://dev.to/julianrubisch/building-a-lightbox-with-hotwire-and-swiperjs-20ig
b. https://qiita.com/nazomikan/items/52bf6b35ff8fa93c029b
a.の記事はturbo_frameで表示してswiperまで使ってますがそこまではまだいいかな…
modal出し入れの仕組みはこの記事をそのまま流用させていただいてます。
b.の記事はstimulusの新機能を解説していただいたもので、この記事のアクションメソッドにパラメータが渡せるようになったを見てこれでいけるんじゃないか?となりました。ありがとうございます。
流れとしては
- アイコン化した画像のlinkにその画像のsoruceをparamに仕込みclickによる発火でparamをstimulus controllerへ渡す
- modalの画像表示箇所をTargetにしてそのsrcにparamの中身を入れる
となります
コード
ここでは仮にArticle modelにphotoというactive_storageがあるとします
まず該当のstimulus controllerです。
名前はlightboxにしてます。
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "modal", "background", "imagePreview" ]
handleOpen(event) {
event.preventDefault()
this.modalTarget.classList.remove("hidden")
this.backgroundTarget.classList.remove("opacity-0")
this.backgroundTarget.classList.add("opacity-100")
this.showPreview(event.params) //<= ここ重点
}
handleClose(event) {
event.preventDefault()
this.modalTarget.classList.add("hidden")
this.backgroundTarget.classList.remove("opacity-100")
this.backgroundTarget.classList.add("opacity-0")
}
showPreview(params) {
this.imagePreviewTarget.src = params.source
}
}
取得したparamをshowPreviewに渡し(event.params
としないとダメでした)、showPreviewでparamsの中身をimagePreviewのsrcに代入してます。
今回paramはobject形式で取り扱っており、paramの名前
(なんでもいい)はsource
にしてます。つまりparamは{source: 画像のurl}
という形でcontrollerは取得するようにします。なのでimagePreviewTarget.src
に代入する際の記述をparams.source
としています。
次に写真(アイコン クリックでモーダル開く)を表示している箇所です
paramの仕込み方はdata-(controller名)-(paramの名前)-param: 仕込むもの
です。
article.photo_previewはdecoratorです。
<div data-controller="lightbox">
<% if !article.photo.blank? %>
<%= link_to "#",
data: { "lightbox-source-param": article.photo_preview,
action: "lightbox#handleOpen" } do %>
<%= article.photo_preview %>
<% end %>
<% end %>
<%= render partial: "shared/_modal" %>
</div>
module ArticleDecorator
def photo_preview
cl_image_path photo.key, gravity: :auto, quality_auto: :good, fetch_format: :auto
end
end
cl_image_pathはcloudinaryの画像加工を利用した画像urlを出力する専用メソッドです。gravity(焦点)、品質、ファイル形式をお任せ(自動)でcloudinary先生によしなにお願いしてます。
普通にviewへimage_tagを出力するときはcl_image_tagメソッドを利用しますが、今回は直接URLを出力してほしいのでこちらを使ってます。ちゃんとこういうの用意してくれてるの有り難いです。
ハマったのはactive_strageの名前(今回はphoto)で呼び出す場合は.key
をつけないとダメだということです。公式ドキュメントのcode例にはしれっと書いてあるのですが、他に文面でのメンションがないので見落としててサポートまで問合せましたよ…
最後にモーダルの写真表示箇所です。
<%= image_tag('data:image/png;base64', data: { "lightbox-target": "imagePreview" }, class: "rounded w-screen h-auto") %>
所感
stimulusの使い方を一つ学べてよかったです。何もかもがふわふわしてますけども想像していたように動くと気持ちいいですね。
今回はrenderingしているpartialのhiddenを付けたり外したりするオールドファッション?なやり方で実装しましたけど、表示されてない要素が裏でrenderingだけされているのはおかしいからturbo使うべし。turboはいいぞ、turbo_frameを使え ってことで、こちらのサイトを参考にしてturbo_frameによる別のモーダル表示機能も作りました。
実装時間自体はどちらも構造をある程度理解していれば(私はしていない)そこまで差はないのではないかという感じがしました。
まだ見慣れないというも間違いなくあリますが、turbo_frameを使うやり方は例えるなら織物の柄を織り込む(言い過ぎ)ような感じがしました。でもrails7を使うならturbo使わないと損してるような気がします。
今回のようにJSメインでやるのはシールを貼ってく感覚に近い気がします。
不要なrenderingを増やす後ろめたさは content-visibility のhiddenが普及すれば考えずにすむようになるのでしょうか?
それでも何か意味不明な罪悪感が拭いきれないような気がします。