RailsのHotwireでCSSアニメーションさせる方法(登録処理もTurbo/Stimlusで実装)

昨日の続きです。
RailsのTurboでAjaxで要素を削除したときにCSSアニメーションさせる方法2(Stimlus化)

画像の登録もTurbo/Stimulusで実施してなかったので修正してみます。

やってみる

css

fade-in用を追加。

.fade-in {
  animation: fade-in .4s linear;
  opacity: 1;
}
.fade-out {
  animation: fade-out .4s linear;
  opacity: 0;
}
@keyframes fade-in {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes fade-out {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

html

登録用にturbo_frame_tagで囲います。animationを追加したいので、data属性にcontrollerも追加。

# show.html.haml
= turbo_frame_tag :user_images, data: { controller: "stream-animations" } do
  = render partial: "image", collection: @user.images, locals: { user: @user }

Stimulsでアニメーション用のcssを取得できるようにdata属性にleaving_classを追加。

# users/_image.html.haml
= turbo_frame_tag "user_image_#{image.id}", data: { entering_class: "fade-in", 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 ×

Railsのcontroller

createでturbo_stream.appendを行います。

class Users::ImagesController < ApplicationController
  def create
    user = User.find(params[:user_id])
    user.images.attach(params[:images])
    image = user.images.last

    render turbo_stream: turbo_stream.append(:user_images, partial: "users/image", locals: { user:, image: })
  end

  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

Stimlus

append用の処理を追加。
template, turboFrameElm の辺りが謎ですが、これで追加される要素が取得できます。
なので、これにアニメーション用のクラスを追加すればokです。

# stream_animations_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    document.addEventListener("turbo:before-stream-render", this.animate)
  }

  disconnect() {
    document.removeEventListener("turbo:before-stream-render", this.animate)
  }

  animate(e) {
    const turboStreamElm = e.target
    const { action, target } = turboStreamElm

    if (action === "append") {
      const template = turboStreamElm.firstElementChild
      const turboFrameElm = template.content.firstElementChild
      const { enteringClass } = turboFrameElm.dataset
      if (enteringClass) {
        turboFrameElm.classList.add(enteringClass)
      }
    } else 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()
        })
      }
    }
  }
}

一応できましたが、、、

stimulsの処理自体は大した行数ではないですが、若干面倒に感じます。cssアニメーションはデフォルト機能として欲しいところ。

参考

Add a `streamElement` property to the `turbo:before-stream-render` event by nbgoodall · Pull Request #20 · hotwired/turbo

See Also

RailsのTurboでAjaxで要素を削除したときにCSSアニメーションさせる方法
RailsのTurboでAjaxで要素を削除したときにCSSアニメーションさせる方法2(Stimlus化)