Ajaxでデータ追加・削除するのにRailsのTurbo便利ですよね。
でも、CSSアニメーションが効かなくて困ってました。
Turbo以前
image-wrapperクラス以下に画像とxボタンを配置します。
# haml .image-wrapper.position-relative = image_tag image = link_to user_image_path(user, image), remote: true, data: { method: :delete, confirm: "Can I delete it?" }, class: "delete-image-link position-absolute top-0 end-0" do %span ×
cssはfade-out用にkeyframes使ってアニメーションを定義します。
.fade-out { animation: fade-out .4s linear; opacity: 0; } @keyframes fade-out { 0% { opacity: 1; } 100% { opacity: 0; } }
あとはjsでfade-outクラスを追加するだけでアニメーションが可能でした。
const deleteImageLinks = document.querySelectorAll(".delete-image-link") if (deleteImageLinks.length) { deleteImageLinks.forEach((link) => { link.addEventListener("click", (e) => { e.preventDefault e.target.closest(".image-wrapper").classList.add("fade-out") }) }) }
まずはTurboで実装
htmlは、turbo_frame_tag で更新部分を囲います。ついでにdata属性はturbo用に書き換えます。
# haml = turbo_frame_tag "user_image_#{image.id}" do .image-wrapper.position-relative = image_tag image = link_to user_image_path(user, image), remote: true, data: { turbo_method: :delete, turbo_confirm: "Can I delete it?" }, class: "delete-image-link position-absolute top-0 end-0" do %span ×
controllerでは、render turbo_stream で削除します。
# controller class Users::ImagesController < ApplicationController def destroy user = User.find(params[:user_id]) user.images.find(params[:id]).purge render turbo_stream: turbo_stream.remove("user_image_#{params[:id]}") end end
自動で更新はできましたが、これではcssアニメーションを追加することができません。
ではどうするか
turbo:before-stream-render という、turboの更新前のイベントを利用し、一時的に処理を止めcss animationが終わってから処理を再開することで対応します。
htmlに大きな違いはありませんが、jsからアニメーション用のクラス名を取得できるようにdata属性に leaving_class を指定しておきます。
# slim
-# ※ data属性に leaving_class を用意する
= turbo_frame_tag "user_image_#{image.id}", data: { leaving_class: "fade-out" } do
.image-wrapper.position-relative
= image_tag image
= link_to user_image_path(user, image), remote: true, data: { turbo_method: :delete, turbo_confirm: "Can I delete it?" }, class: "delete-image-link position-absolute top-0 end-0" do
%span ×
turbo:before-stream-render イベントで、以下のようにすることで対応できました。
document.addEventListener("turbo:before-stream-render", (e) => { const turboStreamElm = e.target const { action, target } = turboStreamElm if (action === "remove") { const turboFrameElm = document.getElementById(target) const { leavingClass } = turboFrameElm.dataset if (leavingClass) { e.preventDefault() turboFrameElm.classList.add(leavingClass) turboFrameElm.addEventListener("animationend", () => { e.target.performAction() }) } } })
所感
正直Turbo以前の方がsimpleですよね。
参考
・Tweet / Twitter
・Turbo Streamsでの削除前にアニメーションを入れる
See Also
・RailsのTurboでAjaxで要素を削除したときにCSSアニメーションさせる方法2(Stimlus化) ・RailsのHotwireでCSSアニメーションさせる方法(登録処理もTurbo/Stimlusで実装)