Sebb
February 22, 2023, 12:49pm
1
When LV change-detection results in new elements rendered, what is the recommended way to do that?
Is this OK? (works, but not sure if right way to do it)
defmodule TestlvtransitionsWeb.MyLive do
use TestlvtransitionsWeb, :live_view
def render(assigns) do
~H"""
<style>
@keyframes fadein {
from { opacity: 0.2; }
to { opacity: 1; }
}
.new-box { animation: fadein 1000ms linear; }
</style>
<div phx-click="show1">SHOW 1</div>
<div phx-click="show2">SHOW 2</div>
<div :if={@show1} class="bg-red-500 fadein">HELLO</div>
<div :if={@show2} class="bg-red-500 fadein">HELLO</div>
"""
end
def mount(_, _, socket), do: {:ok, assign(socket, show1: false, show2: false)}
def handle_event("show1", _, socket), do: {:noreply, assign(socket, :show1, true)}
def handle_event("show2", _, socket), do: {:noreply, assign(socket, :show2, true)}
end
3 Likes
I would give phx-mounted a try
1 Like
Sebb
February 22, 2023, 5:35pm
3
I’m having some trouble with that.
I think I have to match the transition-duration
property value to the time option. (the docs don’t do that, but I do not see how else this should work). OK I did that. Also in class
I set the same opacity as in the start-slot of the 3-tuple, otherwise the element appears-vanishes-appears. But still I do not get a smooth transition, it just pops up.
<div
:if={@show1}
phx-mounted={
JS.transition({"ease-out duration-1000", "opacity-20", "opacity-100"}, time: 1000)
}
class="bg-red-500 opacity-20"
>
There is a solution with JS.show
here: Achieving tailwind enter/leave transitions with LiveView.JS - #7 by paulstatezny
<div
:if={@show1}
style="display: none"
phx-mounted={
JS.show(
transition: {"ease-linear duration-1000", "opacity-20", "opacity-100"},
time: 1000
)
}
class="opacity-20 bg-red-500"
>
Sebb
February 22, 2023, 6:30pm
5
I’d rather not pull in another dependency for this trivial task. The CSS-animation works fine, but I still hope there is a way with LV (expect the hacky solution with JS.show).
1 Like
Take a look at animation-iteration-count
. You don’t need JS or LV to limit an css animation to play only once.
kwando
February 22, 2023, 6:50pm
7
I cant wrap my head around how to reliable use JS hooks to animate. There are some unintuitive things happening when animating stuffs and mixing JS hooks with server rendering problem is probably on my end, but still
2 Likes
Sebb
February 22, 2023, 7:01pm
8
You are answering the questions I did not ask yet, thats next level
Actually trying to figure out one edge case now.
The version in the OP (animate
) works fine (and also once as animation-iteration-count
is set to 1 (implicitly as its the default). But consider this:
<style>
@keyframes fadein { from { opacity: 0.2; } to { opacity: 1; } }
.new-box { animation: fadein 1000ms linear; }
</style>
<div phx-click="show1">SHOW red</div>
<div phx-click="show2">SHOW blue</div>
<div :if={@show1} class="bg-red-500 fadein">HELLO</div>
<div :if={@show2} class="bg-blue-500 fadein">HELLO</div>
When one clicks “SHOW blue” first and then “SHOW red” the animation is blue both times. I have an idea whats happening and maybe one would have to set update-ignore
or sth, but its not nice.
I think its just not really fully working right now.
Yep. I came to the same conclusion
I see that your element doesn’t have a an id
. I think it needs a unique id assigned to work, otherwise LV can’t distinguish newly mounted elements from those already existing. phx-mounted
works for me, but not if I remove the id
. Maybe give it a try
Sebb
February 22, 2023, 7:44pm
10
Indeed, that solves the messed up animations. But it does not heal the JS.transition
solution.
Here are all three approaches I tried: animation, JS.show
(@paulstatezny solution) and JS.transition
.
The first two look the same, the third does not work.
<div phx-click="show1">SHOW red</div>
<div phx-click="show2">SHOW blue</div>
<div class="flex flex-col gap-2">
<div>
<style>
@keyframes fadein {
from { opacity: 0.2; }
to { opacity: 1.0; }
}
.fadein { animation: fadein 1 1000ms linear; }
</style>
<div :if={@show1} id="11" class="bg-red-500 fadein">HELLO ANIM 1</div>
<div :if={@show2} id="21" class="bg-blue-500 fadein">HELLO ANIM 2</div>
</div>
<div>
<div :if={@show1} id="21" style="display: none" class="opacity-20 bg-red-500"
phx-mounted={JS.show(
transition: {"ease-linear duration-1000", "opacity-20", "opacity-100"},
time: 1000)}>HELLO LV.JS/A 1</div>
<div :if={@show2} id="22" style="display: none" class="opacity-20 bg-blue-500"
phx-mounted={JS.show(
transition: {"ease-linear duration-1000", "opacity-20", "opacity-100"},
time: 1000)}>HELLO LV.JS/A 2</div>
</div>
<div>
<div :if={@show1} id="31" class="bg-red-500 opacity-20"
phx-mounted={
JS.transition({"ease-linear duration-1000", "opacity-20", "opacity-100"}, time: 1000)
}>HELLO LV.JS/B 1</div>
<div :if={@show2} id="32" class="bg-blue-500 opacity-20"
phx-mounted={
JS.transition({"ease-linear duration-1000", "opacity-20", "opacity-100"}, time: 1000)
}>HELLO LV.JS/B 2</div>
</div>
</div>
yep forget what I said, I just looked and I’m actually using phx-mounted
+ JS.show
as well… I’m having a strong déjà vu feeling of having gone through all this already
Here’s what works for me:
<div
id={elem.id}
class="hidden"
phx-mounted={
JS.show(transition: {"ease-in duration-500", "opacity-0", "opacity-100"}, time: 500)
}
>
Hello
</div>
Basically, exactly like your solution. I guess thre’s something broken with phx-mounted={JS.transistion(...)}
after all.
3 Likes
Unless I’m mistaken, transitions with JS
functions are altogether broken except when using JS.show
and maybe JS.hide
.
By “broken”, I mean to say that the start and end classes are not applied at the correct times.
I could be wrong, but I’ve spent hours trying many different things to make it work without success.
2 Likes
Sebb
February 23, 2023, 5:20am
13
Yes, seems plausible from what I’ve seen.
I can’t find an issue on Github, maybe I should open one unless someone else comes around and explains it all.
Question why do you all prefer JS.show
and all the tweaks and hacks over a simple CSS-animation? Am I missing sth?
1 Like
kwando
February 23, 2023, 6:49am
15
I’ve also come to the same conclusion. My mental model of how it should work is not overlapping with reality
Sebb
February 23, 2023, 7:37am
16
This does not do what (I think) it should with LV 0.18.15
<div :if={@show1} id="21" class="bg-red-500 opacity-20"
phx-mounted={JS.transition(
{"ease-linear duration-1000", "opacity-20", "opacity-100"},
time: 1000)}>
phoenix 1.7.0-rc.3 1.7.0-rc.3 Up-to-date
phoenix_html 3.3.0 3.3.0 Up-to-date
phoenix_live_view 0.18.15 0.18.15 Up-to-date
tailwind 0.1.10 0.1.10 Up-to-date
Hey @Sebb ,
That library uses JS.dispatch.
defmodule LiveMotion.JS do
@moduledoc ~S'''
Provides functions for triggering client side animations without a round-trip to the server.
The functions in this module follow the same conventions (more or less) like in the
`Phoenix.LiveView.JS` module and are intended to be used instead if you are dealing
with animations. Most of the functions return a `Phoenix.LiveView.JS` struct, so they
can be composed with the LiveView utility functions.
The following utilities are included:
* `animate` - Triggers an animation on an element.
* `toggle` - Toggles between two animations.
* `hide` - Performs an animation and hides the element from the DOM.
* `show` - Shows elements and immediately performs the animation.
Consider the following example to hide an element without a round-trip to the server.
def popcorn(assigns) do
~H"""
This file has been truncated. show original
Is that deprecated already?
Sebb
February 23, 2023, 8:38am
18
this library has its own client-side handler. While it looks very nice, its overkill. After all its working with five lines of CSS.
1 Like
Yup, I went through all of the code and it seems too much.
CSS for the win.
Hopefully it will become as easy as copying an animation from animate.style and pasting it in Live View.
You’re asking why I prefer this:
<div
id={elem.id}
class="hidden"
phx-mounted={
JS.show(transition: {"ease-in duration-500", "opacity-0", "opacity-100"}, time: 500)
}
>
Hello
</div>
to this:
<style>
@keyframes fadein {
from { opacity: 0.2; }
to { opacity: 1.0; }
}
.fadein { animation: fadein 1 1000ms linear; }
</style>
<div :if={@show1} id="11" class="bg-red-500 fadein">HELLO ANIM 1</div>
?
I personally find the first solution simpler and less hacky to be honest. And in general, if I can do something without having to write any CSS, I’m happier
2 Likes