phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.22k stars 933 forks source link

Scroll glitches with phx-viewport-bottom handler taking longer than a couple hundred ms #3462

Closed mxgrn closed 1 month ago

mxgrn commented 1 month ago

Environment

Actual behavior

When handle_event for phx-viewport-bottom takes longer than a couple hundred milliseconds, I'm seeing a scrolling glitch caused (probably) by LiveView calling lastChild.scrollIntoView. The glitch prevents seeing the content that is below the infinite scroll HTML and causes scroll jerking.

Here's the code that's needed to reproduce the issue in a vanilla Phoenix app (let me know if you want a complete app on GitHub).

defmodule InfScrollGlitchWeb.HomeLive do
  use InfScrollGlitchWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def render(assigns) do
    ~H"""
    <div class="container mx-auto">
      <div class="grid grid-cols-2 gap-4" phx-viewport-bottom="load_more" id="grid">
        <!-- Just some nonsense to make the page scrollable -->
        <%= for i <- 1..100 do %>
          <div class="bg-gray-200 p-4 rounded-lg shadow-md" id={"id-#{i}"}>
            <%= i %>
          </div>
        <% end %>
      </div>

      <h2 class="text-3xl my-4">This should be visible while load_more executes</h2>
    </div>
    """
  end

  def handle_event("load_more", _, socket) do
    # This is what triggers the glitch
    :timer.sleep(500)

    {:noreply, socket}
  end
end

Expected behavior

In my example, the content of the h2 tag should be visible while handle_event executes, and no scroll glitching should take place.

shahryarjb commented 1 month ago

Hi dear @mxgrn, I just saw this out of curiosity and of course you'll have to wait for the LiveView team to see if it could be a bug or not.

But in my opinion this is not a bug if you have InfiniteScroll

Extra information For example: This line scroll to the end https://github.com/phoenixframework/phoenix_live_view/blob/main/assets/js/phoenix_live_view/hooks.js#L140 and https://github.com/phoenixframework/phoenix_live_view/blob/main/assets/js/phoenix_live_view/hooks.js#L178 And the last child is your records (number 100) not the h2 which is out of your loop i mean under `phx-viewport-bottom="load_more"` So if you push your h2 under ```html
...

This should be visible while load_more executes

``` you can see your h2 instead of seeing record 100 ### Full changed code ``` def render(assigns) do ~H"""
<%= for i <- 1..100 do %>
<%= i %>
<% end %>

This should be visible while load_more executes

""" end ```

What is your problem

So as you see this link

https://hexdocs.pm/phoenix_live_view/bindings.html#scroll-events-and-infinite-stream-pagination

You should define what time your scroll is finished not to scroll to last record

<ul
  id="posts"
  phx-update="stream"
  phx-viewport-top={@page > 1 && "prev-page"}
  phx-viewport-bottom={!@end_of_timeline? && "next-page"}
  phx-page-loading
  class={[
    if(@end_of_timeline?, do: "pb-10", else: "pb-[calc(200vh)]"),
    if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]")
  ]}
>
....

<div :if={@end_of_timeline?} class="mt-5 text-[50px] text-center">
  🎉 You made it to the beginning of time 🎉
</div>

If you do not use it as a InfiniteScroll, so I think it is better to use phx-hook

chrismccord commented 1 month ago

Thanks for the intel @shahryarjb! I agree the current behavior is working as intended. Thanks!