[Elixir Tip]: Fade-out flash in Phoenix LiveView 0.17 and TailwindCSS

illustrations illustrations illustrations illustrations illustrations illustrations illustrations

[Elixir Tip]: Fade-out flash in Phoenix LiveView 0.17 and TailwindCSS

Published on Aug 18, 2021 by Seungjin Kim

post-thumb

This is a short snippet on how to do fade-out on @flash outputs, for Phoenix LiveView 0.17! Yes the latest one, utilizing Phoenix.LiveView.JS commands.

The effect I was going for was when there are new flashes, the flash would automatically fade out, after waiting a couple seconds, so that the user will be nudged but message will still fade away.

This will do the animation, and then lastly fire a JS.push() to clear out the flash at the server.

Files that are needed are:

  • app.js - to set up event handler to processing event message coming from server, and to run the JS command assigned to target attribute
  • component.ex - your LiveComponent file
  • component.html.heex - code for showing flash, and to have the target attribute, and phx-value-key attribute
  • tailwind.config.js - optional, just showing how to add custom duration / delays if you need them

Check out the code in its entirety here:

import 'phoenix_html'
// assets/js/app.js
import { Socket } from 'phoenix'
import { LiveSocket } from 'phoenix_live_view'
let liveSocket = new LiveSocket('/live', Socket, {
params: { _csrf_token: csrfToken },
...
});
// Connect if there are any LiveViews on the page
liveSocket.connect()
window.liveSocket = liveSocket
// ****** flash event handler for server to dispatch fading out event to dom *******
window.addEventListener('phx:fade-out-flash', (e) => {
const targetAttr = 'data-handle-fadeout-flash'
document.querySelectorAll(`[${targetAttr}]`).forEach((el) => {
const key = el.getAttribute('phx-value-key')
if (key == e.detail.type) {
liveSocket.execJS(el, el.getAttribute(targetAttr))
}
})
})
view raw app.js hosted with ❤ by GitHub
// in tailwind.config.js:
module.exports = {
...
theme: {
...
extend: {
...
transitionProperty: ['visibility'],
transitionDuration: {
2000: '2000ms',
},
transitionDelay: {
2000: '2000ms',
5000: '5000ms',
},
}
}
}
# in your LiveComponent that will handle flashes:
defmodule MyAppWeb.HeaderComponent do
@moduledoc """
Header that shows flashes.
"""
use MyAppWeb, :live_component
@impl true
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)
# this function will look and `push_event()` for each existing flash type
|> trigger_fade_out_flashes(assigns)}
end
# depending on how you structured your code, `socket.assigns.flash` might have your flash map.
# for me I was running this as a component, so it was being passed @flash into `view_flash`.
defp trigger_fade_out_flashes(socket, %{view_flash: nil} = assigns), do: socket
defp trigger_fade_out_flashes(socket, %{view_flash: flash} = assigns) do
# push event for each flash type.
Map.keys(flash)
|> Enum.reduce(socket, fn flash_key, piped_socket ->
piped_socket
|> push_event("fade-out-flash", %{type: flash_key})
end)
end
# use TailwindCSS to wait 2 seconds before starting transition. Afterwards, send event to server to clear out flash.
# `lv:clear-flash` will use `phx-value-key` attribute in element to remove flash per type.
def delayed_fade_out_flash() do
JS.hide(
transition:
{"transition-opacity ease-out delay-2000 duration-1000", "opacity-100", "opacity-0"},
time: 6000
)
|> JS.push("lv:clear-flash")
end
end
<div
role="alert"
data-handle-fadeout-flash={delayed_fade_out_flash()}
phx-value-key="info"
>
<%= live_flash(@view_flash, :info) %>
</div>

Oh, and this uses TailwindCSS. If you don’t use TailwindCSS, you can look at https://tailwindcss.com/docs to get the one-to-one translation from Tailwind to CSS styling.

References:

This is so cool. Not gonna lie. Updating pre-0.15 Phoenix LiveView into 0.17.5 just to do this took me two days, and was just about to give up yesterday.

Before this I relied on a hack to just fade out, but the flash would still be there. It was hard to time the various things going on during an animated fading-out of flashes, because it involved both server and DOM to act in unison:

  1. Fade things out, and LiveView shouldn’t trigger a rerender during that time
  2. AFTER animation is done, remove that flash

What I was doing was very hacky. And if I did want to do it right, I’d have to have a whole pipeline of hook + alpineJS to get it up and running, which seems too much lift for small amount of feature footprint.

But now, it’s so much more streamlined, with very small footprint being created. I love it.